1#!/bin/sh -f
2#
3# Copyright (c) 2008 - 2016
4# Dominic Fandrey <kamikaze@bsdforen.de>
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11#
12# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
13# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
14# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
15# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
16# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
17# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
18# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
19# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22#
23
24# AMD parameters
25amd="%%AMD%%"
26amq="%%AMQ%%"
27a="%%RUN%%/automounter.amd"
28c=4
29w=2
30l="%%RUN%%/automounter.amd.log"
31directory="%%RUN%%/automounter.amd.mnt"
32map="%%RUN%%/automounter.amd.map"
33
34# The amd map that the dynamic mounts are appended to.
35static_map="%%MAP%%"
36
37# RPC tools, required for AMD operation.
38rpcbind="%%RPCBIND%%"
39rpcinfo="%%RPCINFO%%"
40
41# Logger command.
42logger="%%LOGGER%%"
43
44# The location of the devfs.
45devfs="%%DEVFS%%"
46
47# A file to remember for which partitions automounter has been configured.
48#
49# Lines in the file conform to the following format:
50#	<device>;<label>
51#
52# From the glabel detector perspective <device> is the geom consumer and
53# <label> the provider.
54#
55# From the probe detector perspective <device> is the geom provider and
56# <label> is a virtual convenience label.
57nodes="%%VARTMP%%/automounter.nodes"
58
59# A temporary file for the partitions of the last run.
60oldnodes="%%TMP%%/automounter.nodes.old"
61
62# A temporary mountpoint for probing.
63probe="%%TMP%%/automounter.probe"
64
65# Used to store a list of device that have already probed and shouldn't be
66# reprobed. This is used to prevent the adding of devices that have been
67# blacklisted through blacklist_nodes.
68probed="%%TMP%%/automounter.probed"
69
70# Used to keep a list of devices and their appearance time to screen devd
71# events only occuring due to device probing.
72screen="%%TMP%%/automounter.screen"
73
74# The pid of amd.
75pidfile="%%RUN%%/automounter.amd.pid"
76
77# This is where the links will be created to access the file systems.
78linkdir="%%MEDIA%%"
79
80# This is where the folders to mount the file systems into will be created.
81mountdir="%%RUN%%/automounter.mnt"
82
83# Lock file.
84lock="%%RUN%%/automounter.lock"
85
86# Geli is inactive, set this to anything but 0 to activate it.
87geli=0
88
89# A file to remember which geli encrypted providers belong to which node.
90#
91# The file is in the following format:
92# 	<providerName>;<mdDevice>;<keyLabel>
93#
94# The fields are defined as follows:
95# 	providerName	is the filename of the image or device link.
96#	mdDevice	is the device node of the file backed memory disk as
97#			it was returned by mdconfig or the device that was
98#			referenced by a %%DEVFS%% relative symlink.
99#	keyLabel	is the label of the key providing device.
100#
101geli_nodes="%%RUN%%/automounter.geli.nodes"
102
103# A temporary file to recognize which nodes were available during the last run.
104geli_oldnodes="%%TMP%%/automounter.geli.oldnodes"
105
106# A file to remember where we can find which keys.
107#
108# The format is:
109#	<label>;<keyName>
110geli_availablekeys="%%RUN%%/automounter.geli.keys"
111
112# GEOM ELI encrypted images and device link location.
113geli_images="%%GELI_IMAGES%%"
114
115# GEOM ELI keys location.
116geli_keys="%%GELI_KEYS%%"
117
118# Timeout in seconds for aquiring the lock.
119timeout=10
120
121# If set to 1 glabel file system detection is turned on.
122detect_glabel=1
123
124# If set to 1 iso9660 hard coding for devices is turned on.
125detect_iso9660=1
126
127# If set to 1 probe file system detection is turned on.
128detect_probe=1
129
130# The default mount options.
131mount_options=rw,noatime,noexec
132
133# Mount isos as cd9660.
134iso9660=cd9660
135iso9660_options=ro
136
137# Dirty fusefs bug workaround.
138evil_fuse=0
139
140# A comma separated list of file system types to probe geom providers for.
141probe_types=ufs,msdosfs,iso9660,ntfs,ext2fs
142
143# A comma separated list of device patterns that are hardcoded to iso9660.
144iso9660_devs="acd*,cd*"
145
146# Config file.
147config="%%PREFIX%%/etc/automounter.conf"
148
149#HACK
150amd="/usr/sbin/amd"
151amq="/usr/sbin/amq"
152a="/var/run/automounter.amd"
153l="/var/run/automounter.amd.log"
154directory="/var/run/automounter.amd.mnt"
155map="/var/run/automounter.amd.map"
156static_map="/etc/amd.map"
157rpcbind="/usr/sbin/rpcbind"
158rpcinfo="/usr/bin/rpcinfo"
159logger="/usr/bin/logger -st automounter"
160devfs="/dev"
161nodes="/var/tmp/automounter.nodes"
162oldnodes="/tmp/automounter.nodes.old"
163probe="/tmp/automounter.probe"
164probed="/tmp/automounter.probed"
165screen="/tmp/automounter.screen"
166pidfile="/var/run/automounter.amd.pid"
167linkdir="/media"
168mountdir="/var/run/automounter.mnt"
169lock="/var/run/automounter.lock"
170geli_nodes="/var/run/automounter.geli.nodes"
171geli_oldnodes="/tmp/automounter.geli.oldnodes"
172geli_availablekeys="/var/run/automounter.geli.keys"
173geli_images="/var/geli/images"
174geli_keys=".geli/keys"
175config="/usr/local/etc/automounter.conf"
176#hack
177
178# Read config file.
179if [ -e "$config" -a ! -d "$config" ]; then
180	. "$config"
181fi
182
183# devfs must not end with /
184devfs=$(realpath $devfs)
185
186# Freeze the current configuration
187readonly amd amq a c w l directory map static_map rpcbind rpcinfo logger devfs
188readonly nodes oldnodes probe probed screen pidfile linkdir mountdir lock
189readonly geli geli_nodes geli_oldnodes geli_availablekeys geli_images geli_keys
190readonly timeout detect_glabel detect_iso9660 detect_probe mount_options
191readonly evil_fuse probe_types iso9660_devs config
192
193IFS='
194'
195
196# Native error return codes.
197readonly ERR_CMD_UNKNOWN=1
198readonly ERR_NOT_STARTED=2
199readonly ERR_MOUNT_FS_MISSING=3
200readonly ERR_UMOUNT_ACTIVE=4
201readonly ERR_LIST_LOCKED=5
202readonly ERR_RPC_FAIL=6
203readonly ERR_AMD_FAIL=7
204
205# Locking error return codes.
206readonly EX_USAGE=64
207readonly EX_SOFTWARE=70
208readonly EX_OSERR=71
209readonly EX_CANTCREAT=73
210readonly EX_TEMPFAIL=75
211
212# Open a file descriptor to the null devices.
213exec 3> "$devfs/null"
214
215#
216# URL decode, used to make prettier labels under FreeBSD 10.
217#
218# Does not support "+" decoding.
219#
220# @param @
221#	The strings to decode, if not given stdin is decoded
222#
223urldecode() {
224	if [ $# -gt 0 ]; then
225		echo "$@" | urldecode
226		return
227	fi
228	awk '
229	# Create a character symbol table
230	BEGIN {
231		i = -1;
232		while (++i < 256) {
233			chars[sprintf("%%%02X", i)] = i
234			chars[sprintf("%%%02x", i)] = i
235		}
236	}
237
238	# Replace urlencoded characters
239	{
240		i = 1
241		while (i <= length($0)) {
242			char = substr($0, i, 3);
243			if (char in chars) {
244				printf("%c", chars[char])
245				i += 3
246			} else {
247				printf("%.1s", char)
248				i++
249			}
250		}
251		printf(ORS)
252	}'
253}
254
255#
256# Produces a list of geom device nodes on stdout.
257#
258# Only outputs leaf nodes that are not mounted.
259#
260# NOTE, this breaks for labels containing a pipe '|' or or colon ':'
261# character.
262#
263geomDevices() {
264	/usr/sbin/gstat -b -I0 | /usr/bin/awk '
265		# Build a device node tree.
266		NR > 2 && $10 !~ "/" {
267			# Add to list of device nodes.
268			nodes[NR] = $10;
269			# Build a tree containing device occurrences.
270			do {
271				tree[$10]++;
272				sub("[a-z.]+[0-9]*\$", "", $10);
273			} while ($10)
274		}
275		# Output leaves.
276		END {
277			for (i in nodes) {
278				if (tree[nodes[i]] == 1) {
279					print nodes[i];
280				}
281			}
282		}
283	' | /usr/bin/grep -Exv "$(
284		(/sbin/mount -p; /usr/sbin/swapinfo) | /usr/bin/sed -nE "$(
285			geomLabels | /usr/bin/sed 's|.*|s:^/dev/(&)(	+/\| +[0-9]).*:&:p|'
286		)"
287	)"
288}
289
290#
291# Produces a list of geom labels on stdout.
292#
293# The labels follow this format:
294#	<device>|<label>
295#
296geomLabels() {
297	/sbin/glabel list | /usr/bin/awk '
298		/Consumers:/ {name = 1}
299		/Geom name:/ {sub("Geom name: ", ""); printf; name = 0}
300		! name && /1. Name:/ {sub("1. Name: ", "|");print}
301	'
302}
303
304#
305# Removes a set of media links.
306#
307# @param 1
308#	The current mode of operation (i.e. update or stop)
309# @param @
310#	Expects a list of labels and devices in the form:
311#		[<label> <device>]*
312#
313rmLinks() {
314	local mode label pretty device type
315
316	mode="$1"
317	shift
318
319	while [ -n "$1" ]; do
320		label="$1"
321		device="$2"
322		type="${label%%/*}"
323		pretty="$(urldecode "$label")"
324
325		# Prepare for the next iteration.
326		shift 2
327
328		# Make sure this device is not mounted.
329		/sbin/umount -f "$(realpath $mountdir)/$label" >&3 2>&3
330
331		# Remove the stale label.
332		echo "$mode: remove <$pretty> [$device]" | eval $logger
333		/bin/rm "$linkdir/$pretty"
334		/bin/rm "$linkdir/$label" 2>&3 # Pre 1.5 style links
335		/bin/rm "$linkdir/${devfs##*/}/$device.$type"
336		/bin/rm "$linkdir/${devfs##*/}/$device"
337		/bin/rmdir "$linkdir/${devfs##*/}" 2>&3
338		/bin/rmdir "$linkdir/$type" 2>&3
339		/bin/rmdir "$mountdir/$label"
340		/bin/rmdir "$mountdir/$type" 2>&3
341	done
342}
343
344#
345# Creates a set of media links.
346#
347# @param @
348#	Expects a list of labels and devices in the form:
349#		[<label> <device>]*
350#
351createLinks() {
352	local label pretty device type
353
354	while [ -n "$1" ]; do
355		label="$1"
356		device="$2"
357		type="${label%%/*}"
358		pretty="$(urldecode "$label")"
359
360		# Prepare for the next iteration.
361		shift 2
362
363		# Create mount hooks.
364		echo "update: add <$pretty> [$device]" | eval $logger
365		/bin/mkdir -p "$mountdir/$label"
366
367		# Inherit mount node owner from device.
368		/usr/sbin/chown "$(
369			/usr/bin/stat -f %u:%g "$devfs/$label" 2>&3 \
370			|| /usr/bin/stat -f %u:%g "$devfs/$device"
371		)" "$mountdir/$label"
372
373		# Inherit permissions from device. Add the executable
374		# bit if read or write are permitted.
375		/bin/chmod "$(
376			(
377				/usr/bin/stat -f 0%Lp "$devfs/$label" 2>&3 \
378				|| /usr/bin/stat -f 0%Lp "$devfs/$device"
379			) | /usr/bin/tr '246' '357'
380		)" "$mountdir/$label"
381
382		# Create the links that invoke amd.
383		/bin/mkdir -p "$linkdir/$type"
384		/bin/mkdir -p "$linkdir/${devfs##*/}"
385		/bin/ln -s "$directory/$hash" "$linkdir/$pretty"
386		/bin/ln -s "$directory/$hash" "$linkdir/${devfs##*/}/$device.$type"
387		/bin/ln -s "$directory/$hash" "$linkdir/${devfs##*/}/$device"
388	done
389}
390
391#
392# This function is forked by geliUpdate() to unmount images that do not longer
393# have a key available.
394#
395# @param 1
396#	The label of the key holding file system.
397# @param 2
398#	The name of the geli provider to unmount.
399# @param 3
400#	The device the geli provider is available as.
401#
402geliUnmount() {
403	local line label pretty name device
404	label="$1"
405	name="$2"
406	device="$3"
407	line="$name;$device;$label"
408	pretty="$(urldecode "$label")"
409	# Attempt to detach until successful or until the
410	# key has returned.
411	while true; do
412		# Skip if the key to this image has returned.
413		/usr/bin/lockf "$lock" /bin/sh -c "
414			if /usr/bin/grep -qFx '$label;$name' \
415				'$geli_availablekeys'; then
416				exit 0
417			else
418				/sbin/geli detach '$device' \
419					2> '$devfs/null' \
420					|| exit 1
421				/bin/mv '$geli_nodes' \
422					'$geli_oldnodes'
423				/usr/bin/grep -vFx '$line' \
424					'$geli_oldnodes' \
425					> '$geli_nodes'
426				/bin/rm '$geli_oldnodes'
427				exit 2
428			fi
429		"
430		case $? in
431		0)
432			# The key is back, no more
433			# need to detach.
434			exit 0
435			;;
436		2)
437			# Detach succeeded.
438			echo "geli: remove provider <$name> [$device]" \
439				"with key from <$pretty>" | eval $logger
440			break
441			;;
442		esac
443		/bin/sleep "${w:-1}"
444	done
445
446	# Hang around to remove the device node for the image.
447	# Unless it's a device link.
448	if [ -e "$geli_images/$name" ]; then
449		until /sbin/mdconfig -du "$device"; do
450			/bin/sleep "${w:-1}"
451		done
452	fi
453}
454
455#
456# This function checks whether the given device or label is blacklisted.
457#
458# @param 1
459#	The device name of the discovered node.
460# @param 2
461#	The label of the discovered node.
462# @param devices_blacklist
463#	A list of glob patterns to check the device against.
464# @param nodes_blacklist
465#	A list glob patterns to check label nodes against.
466# @return
467#	0 if everything goes right
468#	1 if the device is blacklisted
469#	2 if the label is blacklisted
470#
471checkBlacklists() {
472	local device label
473	device="$1"
474	label="$2"
475
476	# Check weather the device is blacklisted.
477	if [ -n "$devices_blacklist" ]; then
478		eval "
479			case '$device' in
480			$devices_blacklist)
481				return 1
482				;;
483			esac
484		"
485	fi
486
487	# Check weather the partition is blacklisted.
488	if [ -n "$nodes_blacklist" ]; then
489		eval "
490			case '$label' in
491			$nodes_blacklist)
492				return 2
493				;;
494			esac
495		"
496	fi
497}
498
499#
500# This checks whether a revisit after cleanup is needed, because the device
501# was previously discovered with a different label.
502#
503# @param 1
504#	The name of the detector.
505# @param 2
506#	Set this if it is the revisit.
507# @param device
508#	The name of the device
509# @param label
510#	The label of the current device
511# @param revisit
512#	Add the detector if a second update run will be required to track a
513#	label change.
514# @return
515#	0 (true)	if a revisit is required
516#	1 (false)	otherwise
517#
518revisitNeeded() {
519	test -n "$2" && return 1
520
521	# If this was alredy discovered with a different label,
522	# call for a revisit after the cleanup phase.
523	if /usr/bin/grep -q "^$device;" "$oldnodes" \
524		&& ! /usr/bin/grep -qFx "$device;$label" "$oldnodes"
525	then
526		# If not yet in the revisit list, add this detector.
527		if [ "$revisit" = "${revisit%$1}" ]; then
528			revisit="$revisit${revisit:+$IFS}$1"
529		fi
530		return 0
531	fi
532
533	return 1
534}
535
536#
537# This function records a node, creates the map file entry and calls
538# createLinks. The idea is to separate this code from the device discovery
539# code in the update() function.
540#
541# @param 1
542#	The device name of the discovered node.
543# @param 2
544#	The label of the discovered node.
545# @param devices_blacklist
546#	A list of glob patterns to check the device against.
547# @param nodes_blacklist
548#	A list glob patterns to check label nodes against.
549# @return
550#	0 if everything goes right
551#	1 if the device is blacklisted
552#	2 if the label is blacklisted
553#	3 if the file system type is not known, i.e. the label matches label/*
554#
555writeNode() {
556	local device label type hash
557	device="$1"
558	label="$2"
559
560	# Check weather device or label are blacklisted.
561	checkBlacklists "$device" "$label" || return $?
562
563	type="${label%%/*}"
564	mount="$type"
565	options="$mount_options"
566
567	# Skip on unknown types.
568	test "$type" = "label" && return 3
569
570	# Remember node.
571	echo "$device;$label" >> "$nodes"
572
573	# Create node hash for amd.
574	hash="$(/sbin/md5 -qs "$device;$label")"
575
576	echo "
577# $label
578$hash type:=program;fs:=\"$mountdir/$label\";\\
579mount:=\"$0 mount mount $hash\";\\
580unmount:=\"$0 umount umount $hash\"
581" >> "$map"
582
583	# Skip if already present.
584	if ! /usr/bin/grep -qFx "$device;$label" "$oldnodes"; then
585		# Create the media links.
586		createLinks "$label" "$device"
587	fi
588	return 0
589}
590
591#
592# Make a read-only mount in the probe directory. Make sure to run probeUnmount
593# after succeeding.
594#
595# @param 1
596#	The file system type to use for mounting.
597# @param 2
598#	The device to mount. This may also be a label.
599# @return
600#	Whatever the mount command returns, 0 for success,
601#	something else otherwise.
602#
603probeMount() {
604	local type mount options device line status
605	type="$1"
606	device="$2"
607
608	# Create probing directory.
609	/bin/mkdir -p "$probe"
610
611	# Deal with devices given as a label.
612	line="$(/usr/bin/grep -x ".*;$device" "$nodes")"
613	if [ -n "$line" ]; then
614		device="${line%%;*}"
615	fi
616
617	# Check for config file settings.
618	mount="$type"
619	options="$mount_options"
620	if [ -n "$(eval "echo \"\$$type\"")" ]; then
621		mount="$(eval "echo \"\$$type\"")"
622	fi
623	if [ -n "$(eval "echo \"\$${type}_options\"")" ]; then
624		options="$(eval "echo \"\$${type}_options\"")"
625	fi
626
627	# Run the mount command.
628	/sbin/mount -t "$mount" -o "$options${options:+,}ro" \
629	            "$devfs/$device" "$probe" 2>&3
630	status=$?
631	if [ $status -ne 0 ]; then
632		/bin/rmdir "$probe"
633	fi
634	return $status
635}
636
637#
638# Force unmount a probe mount.
639#
640probeUnmount() {
641	/sbin/umount -f "$probe" 2>&3
642	/bin/rmdir "$probe"
643}
644
645#
646# Detect already mounted devices and call writeNode().
647#
648# @param devices_blacklist
649#	A list of glob patterns to check the device against.
650# @param nodes_blacklist
651#	A list glob patterns to check label nodes against.
652#
653mountedDetect() {
654	local line device label
655
656	# Keep mounted nodes.
657	for line in $(/bin/cat "$oldnodes"); do
658		/usr/bin/grep -qFx "$line" "$nodes" && continue
659
660		device="${line%%;*}"
661		label="${line##*;}"
662
663		# Mounted nodes will not be detected by glabelDetect(),
664		# because the label provider is removed as soon as the
665		# consumer is mounted directly, which is what is done since
666		# 1.3.6 to avoid problems with broken labels.
667		if /sbin/mount | /usr/bin/grep -qF "on $(realpath $mountdir)/$label ("; then
668			writeNode "$device" "$label"
669			# Append to the list of already probed devices.
670			echo "$device$IFS$label" >> "$probed"
671		fi
672	done
673}
674
675#
676# Detect labeled devices and call writeNode().
677#
678# @param 1
679#	If set this is considered a revisit.
680# @param devices_blacklist
681#	A list of glob patterns to check the devices against.
682# @param nodes_blacklist
683#	A list glob patterns to check label nodes against.
684# @param revisit
685#	Add glabel if a second update run will be required
686#	to track a label change.
687#
688glabelDetect() {
689	# Check whether glabel based file system discovery has been disabled.
690	test "$detect_glabel" != "1" && return 0
691
692	local line device label
693
694	# Add new mounts.
695	for line in $(geomLabels); do
696		device="${line%%|*}"
697		label="${line#*|}"
698
699		# Use the device name for empty labels.
700		test -z "${label#*/}" && label="$label$device"
701
702		# Skip probed labels. Do not check the device node
703		# in the list of probed devices to be able to follow
704		# label changes.
705		# Instead check for the device in the list of already
706		# discovered nodes.
707		if /usr/bin/grep -qFx "$label" "$probed" \
708			|| /usr/bin/grep -q "^$device;" "$nodes"; then
709			continue
710		fi
711
712		# Do not probe device IDs.
713		case "$label" in
714		*id/*)
715			continue
716		;;
717		esac
718
719		# Record the device to keep the following detectors from
720		# probing.
721		echo "$device" >> "$probed"
722
723		# If this was alredy discovered with a different label, e.g.
724		# because the label took too long to be discovered or was
725		# changed, call for a revisit after the cleanup phase.
726		if revisitNeeded glabel "$1"; then
727			continue
728		fi
729
730		# Write the node.
731		writeNode "$device" "$label"
732
733		# Append to the list of already probed devices.
734		echo "$label" >> "$probed"
735	done
736}
737
738#
739# Detect iso9660 devices without a disk or label and call writeNode().
740#
741# @param 1
742#	If set this is considered a revisit.
743# @param devices_blacklist
744#	A list of glob patterns to check the devices against.
745# @param nodes_blacklist
746#	A list glob patterns to check label nodes against.
747# @param revisit
748#	Add iso9660 if a second update run will be required to track the
749#	disappearance of a label.
750#
751iso9660Detect() {
752	# Check whether iso9660 hard coding has been disabled.
753	test "$detect_iso9660" != "1" && return 0
754
755	local device label dev_pattern
756
757	# Change the iso9660_devs format into something that can be used in a
758	# case statement.
759	dev_pattern="$(
760		echo "$iso9660_devs" | /usr/bin/sed -E \
761			-e 's/^[[:space:]]*,//' -e 's/,[[:space:]]*$//' \
762			-e 's/,/|/g' -e 's,([[:space:]\\]),\\\1,g'
763	)"
764
765	# Add new mounts.
766	for device in $(geomDevices); do
767		# This is in the list of already probed devices.
768		if /usr/bin/grep -qFx "$device" "$probed"; then
769			continue
770		fi
771
772		# Skip if the device is not in the pattern list.
773		eval "
774			case '$device' in
775			$dev_pattern)
776				# pass
777				;;
778			*)
779				continue
780				;;
781			esac
782		"
783
784		label="iso9660/$device"
785
786		# If this was previously discovered with a label, call for
787		# a revisit after the cleanup phase.
788		if revisitNeeded iso9660 "$1"; then
789			continue
790		fi
791
792		# Write the node.
793		writeNode "$device" "$label"
794
795		# Append to the list of already probed devices.
796		echo "$device$IFS$label" >> "$probed"
797	done
798}
799
800#
801# Detect labeled devices and call writeNode().
802#
803# @param 1
804#	If set this is considered a revisit.
805# @param devices_blacklist
806#	A list of glob patterns to check the devices against.
807# @param nodes_blacklist
808#	A list glob patterns to check label nodes against.
809# @param revisit
810#	Add probe if a second update run will be required to track the
811#	disappearance of a label or a file system type change.
812#
813probeDetect() {
814	# Check whether probing based file system discovery has been disabled.
815	test "$detect_probe" != "1" && return 0
816
817	local device label type mount options types
818
819	# Reformat probe_types.
820	types="$(echo "$probe_types" | /usr/bin/egrep -o '[^,]+')"
821
822	# Add new mounts.
823	for device in $(geomDevices); do
824		# This is in the list of already probed devices.
825		if /usr/bin/grep -qFx "$device" "$probed"; then
826			continue
827		fi
828
829		# Try probing every file system type.
830		for type in $types; do
831			label="$type/$device"
832			mount="$type"
833			options="$mount_options"
834
835			# Do not probe blacklisted devices.
836			if ! checkBlacklists "$device" "$label"; then
837				continue
838			fi
839
840			# Check for config file settings.
841			if [ -n "$(eval "echo \"\$$type\"")" ]; then
842				mount="$(eval "echo \"\$$type\"")"
843			fi
844			if [ -n "$(eval "echo \"\$${type}_options\"")" ]; then
845				options="$(eval "echo \"\$${type}_options\"")"
846			fi
847
848			# Try to mount the device.
849			if probeMount "$type" "$device"; then
850				# Unmount the probe.
851				probeUnmount
852
853				# If this was previously discovered with a
854				# label, or a different file system type, call
855				# for a revisit after the cleanup phase.
856				if revisitNeeded probe "$1"; then
857					# Skip the following probes on this
858					# device.
859					break
860				fi
861
862				# Write the node.
863				writeNode "$device" "$label"
864
865				# Append to the list of already probed devices.
866				echo "$device" >> "$probed"
867
868				# Skip the following probes on this device.
869				break
870			fi
871
872			# Add the label to the list of probed devices.
873			echo "$label" >> "$probed"
874		done
875	done
876}
877
878#
879# Update the list of devices to screen from devd events, because they are
880# part of this update run.
881#
882updateScreen() {
883	(geomDevices; geomLabels) \
884		| /usr/bin/lockf -k "$screen" /bin/sh -c "/bin/cat > '$screen'"
885}
886
887#
888# Update the list of managed devices.
889#
890# Updates the amd.map, the list of partitions and the mount links, if amd
891# is running.
892#
893# @return
894#	ERR_NOT_STARTED, if automounter has not been started, otherwise 0.
895#
896update() {
897	local device line partitions label type options
898	local hash owner mode key detector revisit
899	local devices_blacklist nodes_blacklist
900
901	# Don't do anything if amd is not running.
902	# This prevents automounter from starting too early from devd.
903	/bin/sync
904	test -e "$pidfile" || return $ERR_NOT_STARTED
905
906	# Record the currently present devices for screening devd calls.
907	updateScreen
908
909	# Start building a new map by copying the static one.
910	/bin/cp "$static_map" "$map"
911
912	/bin/mkdir -p "$a"
913	/usr/bin/touch "$nodes"
914	/bin/mv "$nodes" "$oldnodes"
915	/usr/bin/touch "$nodes"
916	echo > "$probed"
917
918	# Change the blacklists' format into something that can be used in a
919	# case statement.
920	devices_blacklist="$(
921		echo "${blacklist_devs}" | /usr/bin/sed -E \
922			-e 's/^[[:space:]]*,//' -e 's/,[[:space:]]*$//' \
923			-e 's/,/|/g' -e 's,([[:space:]\\]),\\\1,g'
924	)"
925	nodes_blacklist="$(
926		echo "${blacklist_nodes}" | /usr/bin/sed -E \
927			-e 's/^[[:space:]]*,//' -e 's/,[[:space:]]*$//' \
928			-e 's/,/|/g' -e 's,([[:space:]\\]),\\\1,g'
929	)"
930
931	# Run detectors.
932	revisit=
933	for detector in mounted iso9660 glabel probe mounted; do
934		/bin/sync
935		${detector}Detect
936	done
937
938	# Remove no longer listed nodes.
939	for line in $(/usr/bin/grep -vFx "$(/bin/cat "$nodes")" "$oldnodes"); do
940		device="${line%%;*}"
941		label="${line##*;}"
942
943		# Remove the stale media links.
944		rmLinks update "$label" "$device"
945	done
946
947	# Revisit detectors requesting it.
948	for detector in $revisit; do
949		/bin/sync
950		${detector}Detect revisit
951	done
952
953	# Update geli managed mounts.
954	geliUpdate
955
956	# Finally reload amd map.
957	eval $amq -f
958
959	# Clean up.
960	/bin/rm "$oldnodes" "$probed" 2>&3
961
962	return 0
963}
964
965#
966# Update geli encrypted providers. Requires the oldnodes file to be intact.
967#
968geliUpdate() {
969	# Do not run if geli features are not activated.
970	test "$geli" != "1" && return 0
971
972	local oldkeys keys key label pretty name line device
973
974	# Clean up keys from stale mounts.
975	/usr/bin/touch "$oldnodes"
976	keys="$(/bin/cat "$geli_availablekeys" 2>&3)"
977	oldkeys="$keys"
978	for line in $(/bin/cat "$oldnodes"); do
979		# Skip still present lines.
980		/usr/bin/grep -qFx "$line" "$nodes" && continue
981		label="${line##*;}"
982		label="${label%;*}"
983		pretty="$(urldecode "$label")"
984
985		# Forget keys belonging to this line.
986		for key in $(echo "$keys" | /usr/bin/grep -E "^$label;"); do
987			echo "geli: remove key <${key##*;}> from <$pretty>" | eval $logger
988		done
989		keys="$(echo "$keys" | /usr/bin/grep -Ev "^$label;")"
990	done
991
992	# Now we check new mounts for keys.
993	echo "$keys" | /usr/bin/grep -xv '' > "$geli_availablekeys"
994
995	for line in $(/bin/cat "$nodes"); do
996		/usr/bin/grep -qFx "$line" "$oldnodes" && continue
997		label="${line#*;}"
998		device="${line%%;*}"
999		pretty="$(urldecode "$label")"
1000
1001		if probeMount "${label%%/*}" "$device"; then
1002			for key in $(/bin/ls "$probe/$geli_keys/" 2>&3); do
1003				echo "geli: add key <$key> from <$pretty>" | eval $logger
1004				echo "$label;$key" >> "$geli_availablekeys"
1005			done
1006
1007			probeUnmount
1008		fi
1009	done
1010
1011	# Look for stale images.
1012	/usr/bin/touch "$geli_nodes"
1013	/bin/mv "$geli_nodes" "$geli_oldnodes"
1014	for line in $(/bin/cat "$geli_oldnodes"); do
1015		name="${line%%;*}"
1016		device="${line#*;}"
1017		device="${device%;*}"
1018		label="${line##*;}"
1019
1020		# This line must remain until the node has been destroyed.
1021		echo "$line" >> "$geli_nodes"
1022
1023		# Skip if the key to this image is still around.
1024		if /usr/bin/grep -qFx "$label;$name" "$geli_availablekeys"; then
1025	 		continue
1026		fi
1027
1028		# This image is stale. Is that a new discovery?
1029		if echo "$oldkeys" | /usr/bin/grep -qFx "$label;$name"; then
1030 			# Fork a process that tries to unmount it.
1031			geliUnmount "$label" "$name" "$device" &
1032		fi
1033	done
1034	/usr/bin/touch "$geli_nodes"
1035	/bin/rm "$geli_oldnodes"
1036
1037	# Attach encrypted devices. If necessary create them from images.
1038	for key in $(/bin/cat "$geli_availablekeys"); do
1039		label="${key%;*}"
1040		name="${key##*;}"
1041		pretty="$(urldecode "$label")"
1042
1043		# The image with this name is already available as an md device.
1044		/usr/bin/grep -qE "^$name;" "$geli_nodes" && continue
1045
1046		# There is no image with this name so there is nothing to do.
1047		test -e "$geli_images/$name" \
1048			-o -L "$geli_images/$name" || continue
1049
1050		# Being here means that this image has not yet been made
1051		# available. So it is time to give it a try.
1052		key="$probe/$geli_keys/$name"
1053
1054		probeMount "${label%%/*}" "$label"
1055
1056		# Get the device to attach.
1057		if [ -L "$geli_images/$name" -a ! -e "$geli_images/$name" ]
1058		then
1059			# Get the device name from a link.
1060			device="$(/usr/bin/readlink "$geli_images/$name")"
1061		else
1062			# Create a file backed memory disk.
1063			device="$(/sbin/mdconfig -f "$geli_images/$name")"
1064			while [ ! -e "$devfs/$device" ]; do
1065				/bin/sleep 0.1
1066			done
1067		fi
1068		# Attempt to attach (decrypt) file.
1069		if cd "$(/usr/bin/dirname "$key")" \
1070			&& /sbin/geli attach -p -k "$key" "$device"
1071		then
1072			# Remember success.
1073			echo "geli: add provider" \
1074			     "<$name> [$device] with key from <$pretty>" \
1075			     | eval $logger
1076			echo "$name;$device;$label" >> "$geli_nodes"
1077		else
1078			# Unsuccessful, clean up memory disk.
1079			/sbin/mdconfig -du "$device"
1080		fi
1081
1082		probeUnmount
1083	done
1084
1085	return 0
1086}
1087
1088#
1089# Setup amd if not yet running and call update.
1090#
1091# @param 1
1092#	If set the update call is forked into the background.
1093#
1094start() {
1095	local pid
1096
1097	if [ ! -e "$pidfile" ]; then
1098		# AMD requires rpcbind
1099		if ! eval $rpcinfo >&3; then
1100			eval $logger start: Starting rpcbind ...
1101			if ! eval $rpcbind; then
1102				eval $logger start: failed.
1103				return $ERR_RPC_FAIL
1104			fi
1105			eval $logger start: done.
1106		fi
1107
1108		# Start amd with a copy of the static map
1109		/bin/cp "$static_map" "$map"
1110		eval $logger start: Starting amd ...
1111		if ! eval $amd -p -a "$a" ${c:+-c "$c"} ${w:+-w "$w"} \
1112		          ${l:+-l"$l"} "$directory" "$map" > "$pidfile"; then
1113			eval $logger start: failed.
1114			return $ERR_AMD_FAIL
1115		fi
1116		eval $logger start: done.
1117	fi
1118
1119	if [ -n "$1" ]; then
1120		$0 update &
1121	else
1122		update
1123	fi
1124}
1125
1126#
1127# Kills the amd, unmounts all mounted partitions and cleans up everything.
1128#
1129stop() {
1130	local pid type label line device
1131
1132	pid="$(/bin/cat "$pidfile" 2>&3)"
1133	if [ -n "$pid" ]; then
1134		eval $logger stop: Stopping amd ...
1135		/bin/kill "$pid"
1136		/bin/pwait "$pid" 2>&3
1137		eval $logger stop: done.
1138	fi
1139
1140	# Clean up stale mounts.
1141	for line in $(/bin/cat "$nodes" 2>&3); do
1142		label="${line##*;}"
1143		device="${line%%;*}"
1144
1145		rmLinks stop "$label" "$device"
1146	done
1147
1148	# Clean up stale geli nodes.
1149	for line in $(/bin/cat "$geli_nodes" 2>&3); do
1150		device="${line#*;}"
1151		device="${device%;*}"
1152
1153		/sbin/geli detach -f "$device"
1154		/sbin/mdconfig -du "$device"
1155	done
1156
1157	# Clean up temporary folders.
1158	/bin/rmdir "$a" "$directory" "$mountdir" 2>&3
1159	/bin/rm "$pidfile" "$map" "$nodes" \
1160		 "$geli_availablekeys" "$geli_nodes" 2>&3
1161	/usr/bin/lockf "$screen" true
1162
1163	return 0
1164}
1165
1166#
1167# List mounted, labels, keys, encrypted providers or one of these categories.
1168#
1169# @param 1
1170#	Either "mounted", "labels", "keys" or "encrypted". If given only this
1171#	category will be listed.
1172# @return
1173#	ERR_LIST_LOCKED if the lock is currently held, 0 otherwise.
1174#
1175list() {
1176	# This is not a reliable way to ensure that everything will go right,
1177	# but the probabality that bogus output will appear is rather low
1178	# and not aquiring the lock allows everyone to use the list command.
1179	if [ -e "$lock" ]; then
1180		echo "Locked." 1>&2
1181		return $ERR_LIST_LOCKED
1182	fi
1183
1184	local image device hash label line
1185
1186	# List mounted media.
1187	if [ -z "$1" -o "$1" = "mounted" ]; then
1188		for line in $(
1189			# Format: <device>;<label>
1190			/sbin/mount | /usr/bin/grep -F " $(realpath $mountdir 2>&3)" | \
1191				/usr/bin/sed -E "s,$devfs/(.+) on $(realpath $mountdir 2>&3)/(.*) \(.*,\1;\2,1"
1192		); {
1193			# If the given devices match a line just print
1194			# it and we are done. Otherwise look for the label
1195			# with a different device.
1196			# This is the case for labeled devices, because the
1197			# label is used for mounting and for fuse devices,
1198			# because fuse creates a new device node for each
1199			# device it mounts.
1200			device="${line%%;*}"
1201			if /usr/bin/grep -qFx "$line" "$nodes" 2>&3; then
1202				echo "mounted: <${line#*;}> [$device]"
1203			elif line="$(/usr/bin/grep -E ";${line#*;}$" "$nodes" 2>&3)"; then
1204				if [ "${line#*;}" != "$device" ]; then
1205					echo "mounted: <${line#*;}> [${line%%;*}] as [$device]"
1206				else
1207					echo "mounted: <${line#*;}> [${line%%;*}]"
1208				fi
1209			fi
1210		}
1211	fi | urldecode
1212
1213	# Print the labels that are available for mounting.
1214	if [ -z "$1" -o "$1" = "labels" ]; then
1215		/usr/bin/sed -E 's/([^;]*);(.*)/label: <\2> [\1]/1' \
1216			"$nodes" 2>&3
1217	fi | urldecode
1218
1219	# List the keys that have been found on mounted devices.
1220	if [ -z "$1" -o "$1" = "keys" ]; then
1221		/usr/bin/sed -E 's/(.*);(.*)/key: <\2> from <\1>/1' \
1222			"$geli_availablekeys" 2>&3
1223	fi | urldecode
1224
1225	# List the encrypted providers and their status.
1226	if [ -z "$1" -o "$1" = "encrypted" -o "$1" = "images" ]; then
1227		/usr/bin/sed -E 's/(.*);(.*);(.*)/encrypted provider: <\1> [\2] with key from <\3>/1' \
1228			"$geli_nodes" 2>&3
1229		for image in $(/bin/ls "$geli_images/" 2>&3); do
1230			/usr/bin/grep -qx "$image;.*" "$geli_nodes" 2>&3 \
1231			&& continue
1232			if [ -e "$geli_images/$image" ]; then
1233				echo "encrypted provider: <$image>"
1234			else
1235				device="$(/usr/bin/readlink \
1236					"$geli_images/$image")"
1237				echo "encrypted provider: <$image> [$device]"
1238			fi
1239		done
1240	fi | urldecode
1241
1242	return 0
1243}
1244
1245#
1246# List data in machine readable form using absolute path names.
1247#
1248# @param 1
1249#	Either "mounted", "llinks" for labeled links or "dlinks" for device
1250#	links.
1251# @return
1252#	ERR_LIST_LOCKED if the lock is currently held, 0 otherwise.
1253#
1254mlist() {
1255	# This is not a reliable way to ensure that everything will go right,
1256	# but the probabality that bogus output will appear is rather low
1257	# and not aquiring the lock allows everyone to use the list command.
1258	if [ -e "$lock" ]; then
1259		return $ERR_LIST_LOCKED
1260	fi
1261
1262	# Print mounted file systems.
1263	if [ -z "$1" -o "$1" = "mounted" ]; then
1264		for line in $(
1265			# Format: <device>;<label>
1266			/sbin/mount | /usr/bin/grep -F " $(realpath $mountdir 2>&3)" | \
1267				/usr/bin/sed -E "s,$devfs/(.+) on $mountdir/(.*) \(.*,\1;\2,1"
1268		); {
1269			# If the given devices match a line just print
1270			# it and we are done. Otherwise look for the label
1271			# with a different device, which can happen with
1272			# fuse devices.
1273			if /usr/bin/grep -qFx "$line" "$nodes" 2>&3; then
1274				echo "$mountdir/${line#*;}"
1275			elif $(line="$(/usr/bin/grep -E ";${line#*;}$" "$nodes" 2>&3)"); then
1276				device="${line%%;*}"
1277				echo "$mountdir/${line#*;}"
1278			fi
1279		}
1280	fi
1281
1282	# Print the labels that are available for mounting.
1283	if [ -z "$1" -o "$1" = "llinks" ]; then
1284		/usr/bin/sed "s,[^;]*;,$linkdir/," "$nodes" 2>&3
1285	fi | urldecode
1286
1287	# Print the devices that are available for mounting.
1288	if [ -z "$1" -o "$1" = "dlinks" ]; then
1289		/usr/bin/sed -E "s,([^;]*);([^/]*).*,$linkdir/${devfs##*/}/\1\\$IFS$linkdir/${devfs##*/}/\1.\2," "$nodes" 2>&3
1290	fi
1291
1292	return 0
1293}
1294
1295#
1296# This calls the apropriate mount command for a given hash.
1297#
1298# @param $1
1299#	The hash over the name of the device node to mount.
1300# @return
1301#	ERR_MOUNT_FS_MISSING if the file system to mount cannot be identified,
1302#	otherwise "exec /sbin/mount" is called.
1303#
1304mount() {
1305	local label labels hash device
1306
1307	# Go through the list of configured partitions.
1308	labels="$(/bin/cat "$nodes")"
1309	for label in $labels; do
1310		hash="$(/sbin/md5 -qs "$label")"
1311
1312		# The current partition is the one we're supposed to mount.
1313		if [ "$hash" = "$1" ]; then
1314			device="${label%%;*}"
1315			label="${label#*;}"
1316			type="${label%%/*}"
1317			mount="$type"
1318			options="$mount_options"
1319
1320			# Check for config file settings.
1321			if [ -n "$(eval "echo \"\$$type\"")" ]; then
1322				mount="$(eval "echo \"\$$type\"")"
1323			fi
1324			if [ -n "$(eval "echo \"\$${type}_options\"")" ]; then
1325				options="$(eval "echo \"\$${type}_options\"")"
1326			fi
1327
1328			# When a label consumer is mounted, the label provider
1329			# is destroyed, which creates unnecessary devd noise,
1330			# i.e. the label disappears and re-appears when
1331			# mounting and unmounting, causing lots of obsolete
1332			# update calls through devd.
1333			# Using the label for mounting avoids this.
1334			if [ -e "$devfs/$label" ]; then
1335				device="$label"
1336			fi
1337
1338			# Mount the file system, try read-only fallback in
1339			# case of failure.
1340			/sbin/mount -t "$mount" -o "$options" \
1341			            "$devfs/$device" "$mountdir/$label" \
1342			|| /sbin/mount -t "$mount" -o "$options${options:+,}ro" \
1343			               "$devfs/$device" "$mountdir/$label"
1344			return
1345		fi
1346	done
1347	eval $logger mount: Requested file system not found.
1348	return $ERR_MOUNT_FS_MISSING
1349}
1350
1351#
1352# This just exists to work around bugs in fusefs. It simply calls the
1353# umount command of the OS unless evil_fuse is set to 1.
1354#
1355# @param $1
1356#	The hash over the name of the device node to unmount.
1357# @return
1358#	ERR_UMOUNT_ACTIVE if the file system to be unmounted is active,
1359#	otherwise "exec /sbin/umount" is called.
1360#
1361umount() {
1362	local label pretty device labels status
1363
1364	# Find the label for the current hash.
1365	label=
1366	device=
1367	labels="$(/bin/cat "$nodes")"
1368	for label in $labels; do
1369		if [ "$1" = "$(/sbin/md5 -qs "$label")" ]; then
1370			device="${label%%;*}"
1371			label="${label#*;}"
1372			break
1373		else
1374			label=
1375		fi
1376	done
1377
1378	# If the label is unknown, claim the device was unmounted.
1379	if [ -z "$label" ]; then
1380		return 0
1381	fi
1382
1383	# If the label is not mounted, claim a successful unmount.
1384	if ! /sbin/mount | /usr/bin/grep -qF "$(realpath $mountdir)/$label ("; then
1385		return 0
1386	fi
1387
1388	# Find out if we are a fuse file system and use dirty bug workaround.
1389	# This will lead to unexpected results with more than one fuse based
1390	# file system around. Fuse based file systems will only get unmounted
1391	# if files are not opened on any of them.
1392	if [ "$evil_fuse" = "1" ]; then
1393		local type
1394		type="$(
1395			/sbin/mount | /usr/bin/grep -F "$label" | \
1396				/usr/bin/sed -E "s,.* on $(realpath $mountdir)/$label \(([^,)]*).*,\1,1"
1397		)"
1398
1399		# If there are any files opened on ANY fuse based file system
1400		# then we will NOT unmount.
1401		if [ "$type" = "fusefs" ]; then
1402			/usr/bin/fstat | \
1403				/usr/bin/grep -qE '\?\(fuse\)' && return $ERR_UMOUNT_ACTIVE
1404		fi
1405	fi
1406
1407	# If the device is missing (i.e. it was removed while being mounted),
1408	# force umount.
1409	pretty="$(urldecode "$label")"
1410	if [ ! -e "$devfs/$device" ]; then
1411		echo  "umount: force umount missing <$pretty> [$device]" | eval $logger
1412		# Schedule an update.
1413		/sbin/umount -f "$(realpath $mountdir)/$label" 2>&3
1414		status=$?
1415		update
1416		return $status
1417	fi
1418
1419	# Don't attempt umounts for active file systems.
1420	if /usr/bin/fstat | /usr/bin/grep -qF " $(realpath $mountdir)/$label "; then
1421		return $ERR_UMOUNT_ACTIVE
1422	fi
1423
1424	# Give over to the system unmount command.
1425	exec /sbin/umount "$(realpath $mountdir)/$label" 2>&3
1426}
1427
1428#
1429# For devd events, screens update calls for the usual timeout period unless
1430# a new device appears.
1431#
1432devd() {
1433
1434	local devs
1435
1436	devs="$(geomDevices; geomLabels)"
1437	/usr/bin/lockf -k "$screen" /bin/sh -c "
1438		# Pass and update, so that only the first one passes
1439		pass() {
1440			echo '$devs' > '$screen'
1441			exit 0
1442		}
1443
1444		# Check age
1445		passed=\$((\$(/bin/date +%s) - \$(/usr/bin/stat -f %m -t %s '$screen')))
1446		test \$passed -gt $timeout && pass
1447
1448		screened=\"\$(/bin/cat '$screen')\"
1449
1450		# Check whether new devices appeared
1451		test -n \"\$(echo '$devs' | /usr/bin/grep -vFx \"\$screened\")\" && pass
1452
1453		# Check whether devices disappeared
1454		test -n \"\$(echo \"\$screened\" | /usr/bin/grep -vFx '$devs')\" && pass
1455
1456		# Do not pass
1457		exit 1
1458	"
1459}
1460
1461#
1462# This function initializes the formatting strings and variables for the
1463# monitor list.
1464#
1465# The last terminal character is never used, because some terminals do not
1466# support printing it without starting a new line.
1467#
1468# @param co
1469#	Set to the number of terminal character columns
1470# @param li
1471#	Set to the number of terminal lines
1472# @param hformat
1473#	Set to the printf list heading formatting string
1474# @param lformat
1475#	Set to the printf list formatting string
1476# @param clrformat
1477#	Set to a string that clears a line of output
1478#
1479monitorFormats() {
1480	local wd wl
1481
1482	/usr/bin/tput cl
1483
1484	co=$(/usr/bin/tput co)
1485	li=$(/usr/bin/tput li)
1486
1487	wd=$(((co - 12) / 2))
1488	wl=$((co - 12 - wd))
1489	hformat="%-$wl.${wl}s %-$wd.${wd}s %4.4s %4.4s\r"
1490
1491	lformat="%-$wl.${wl}s %-$wd.${wd}s %3.3s  %3.3s\r"
1492	clrformat="%$((co - 1))s\r"
1493}
1494
1495#
1496# Idles for the desired cycle.
1497#
1498# This works by waiting for a previously issued sleep call. Once waiting
1499# is complete (or was interrupted by a signal), a new sleep call is issued
1500# and $sleepPID is updated.
1501#
1502# @note
1503#	Requires an initial call of monitorIdleInit() to start the first
1504#	sleep cycle
1505# @param idleCycle
1506#	The idle time
1507# @param idleSleepPID
1508#	The PID of the last sleep call
1509#
1510monitorIdle() {
1511	wait $idleSleepPID >&3 2>&3
1512	(
1513		set -T
1514		trap 'kill $(jobs -p)' winch info
1515		/bin/sleep $idleCycle
1516	) >&- 2>&- &
1517	idleSleepPID=$!
1518}
1519
1520#
1521# Initializes the first idle cycle for monitorIdle().
1522#
1523# It also checks whether $idleCycle is set up properly.
1524#
1525# @param idleCycle
1526#	The idle time
1527# @param idleSleepPID
1528#	The PID of the last sleep call
1529#
1530monitorIdleInit() {
1531	idleCycle=${idleCycle%%$IFS*}
1532	if ! echo $idleCycle | /usr/bin/grep -Eqx '([0-9]+\.?[0-9]*|\.[0-9]+)'; then
1533		return 1
1534	fi
1535	(
1536		set -T
1537		trap 'kill $(jobs -p)' winch info
1538		/bin/sleep $idleCycle
1539	) 1>&3 2>&3 &
1540	idleSleepPID=$!
1541}
1542
1543#
1544# This function draws a list heading.
1545#
1546# @param @
1547#	The headers to draw
1548# @param i
1549#	Set to the first line behind the heading and expected to contain
1550#	the line to draw the heading to. Note that a heading is only
1551#	drawn if there are 3 more lines of space (an empty line, the
1552#	heading and space for a line of content
1553# @param hformat
1554#	The formatting string for the line
1555# @param clrformat
1556#	The formatting string for the clear line
1557# @param li
1558#	The number of terminal lines
1559# @retval 0
1560#	The heading was drawn
1561# @retval 1
1562#	Insufficient number of lines left
1563#
1564monitorListHeading() {
1565	# Check for sufficient space
1566	if [ $((i + 2)) -ge $li ]; then
1567		return 1
1568	fi
1569
1570	# Clear the line
1571	/usr/bin/tput cm 0 $i
1572	printf $clrformat
1573	i=$((i + 1))
1574	# Print the headings
1575	/usr/bin/tput cm 0 $i
1576	printf $hformat "$@"
1577	i=$((i + 1))
1578}
1579
1580#
1581# Prints a list row.
1582#
1583# @param @
1584#	The list column entries
1585# @param i
1586#	Set to the line behind the new row, expected to point to the line
1587#	to draw to
1588# @param lformat
1589#	The formatting string for the list entry row
1590# @param li
1591#	The number of terminal lines
1592# @retval 0
1593#	Printing completed
1594# @retval 1
1595#	There was no space to print the row
1596#
1597monitorListRow() {
1598	# No space left to print the row
1599	if [ $i -ge $li ]; then
1600		return 1
1601	fi
1602
1603	# Print row
1604	/usr/bin/tput cm 0 $i
1605	printf "$lformat" "$@"
1606	i=$((i + 1))
1607}
1608
1609#
1610# Cleans up list rows that are no longer in use, because the list got
1611# shorter since the last call.
1612#
1613# @param i
1614#	Expected to point to the first line to clear
1615# @param tailLine
1616#	Set to the end of the tail
1617# @param li
1618#	The number of terminal lines
1619#
1620monitorListTail() {
1621	local line
1622	line=$i
1623	while [ $i -lt ${tailLine:-0} ]; do
1624		test $i -ge $li && break
1625		/usr/bin/tput cm 0 $i
1626		printf $clrformat
1627		i=$((i + 1))
1628	done
1629	tailLine=$line
1630}
1631
1632#
1633# Draws the amd/automounter state heading.
1634#
1635# @param 1
1636#	The number of labels
1637# @param 2
1638#	The number of mounted devices
1639# @param 3
1640#	The number of encrypted images/devices
1641# @param 4
1642#	The number of keys
1643# @param i
1644#	Set to the line behind the heading
1645# @param co
1646#	The number of available terminal character columns
1647#
1648monitorHeading() {
1649	local header amdPID state
1650	# Get the amd and automounter states.
1651	amdPID=$(/bin/cat "$pidfile" 2>&3)
1652	state=up
1653	test -z "$amdPID" && state=down
1654	test -e "$lock" && state=LOCKED
1655
1656	# Print the header lines.
1657	header=$(printf "amd pid: %-5.5s   automounter: %-6.6s" ${amdPID:--} $state)
1658	/usr/bin/tput cm 0 0
1659	printf "%-$((co - 10)).$((co - 10))s %s\r" $header $(/bin/date +%H:%M:%S)
1660
1661 	header=$(printf "state: %3.3s labels,%3.3s mounted,%3.3s encrypted,%3.3s keys" "$@")
1662	/usr/bin/tput cm 0 1
1663	printf "%-$((co - 1)).$((co - 1))s\r" $header
1664
1665	# Point the current line counter behind the headers.
1666	i=2
1667}
1668
1669#
1670# Sets up file descriptors, traps and the terminal cursor for operation.
1671#
1672# The terminal size update trap sets winch=1.
1673#
1674# @param winch
1675#	Is set to 1 by the winch trap to announce that a window change
1676#	happened
1677#
1678monitorSetup() {
1679	trap 'monitorFormats; winch=1' winch info
1680	trap 'exit 0' int term
1681	trap '/usr/bin/tput ve; /usr/bin/tput cl' EXIT
1682	/usr/bin/tput vi
1683}
1684
1685#
1686# Provides a top-like display of the available labels, encrypted devices and
1687# keys.
1688#
1689# No list updates are performed while the automounter is in locked state. The
1690# monitor itself does not acquire the lock, so there is a small chance of
1691# displaying corrupted data, which heals itself during the next cycle.
1692#
1693# @note
1694#	This function is a dead end, the intended way to terminate it is
1695#	a SIGTERM or SIGINT, which result in a call of "exit 0".
1696# @note
1697#	SIGINFO causes a redraw of the current display.
1698# @param 1
1699#	The (optional) update cycle time
1700#
1701monitor() {
1702	# For monitorSetup
1703	local winch
1704	# Populated by monitorFormats()
1705	local co li lformat hformat clrformat
1706	# For monitorIdle()
1707	local idleCycle idleSleepPID
1708	# For monitorHeading() and monitorList*()
1709	local i
1710	# For monitorListTail()
1711	local tailLine
1712
1713	# Initialise traps, cursor and the file descriptor 3 for litter.
1714	monitorSetup
1715
1716	idleCycle=${1:-2}
1717	if ! monitorIdleInit; then
1718		echo "The cycle time $idleCycle is not a valid number of seconds." 1>&2
1719		exit 1
1720	fi
1721
1722	# Initialise list formatting strings.
1723	monitorFormats
1724
1725	# Local variables.
1726	local list label pretty device mounts
1727	local dir mount mode keys nkeys key
1728	local gnodes image encrypted
1729
1730	while true; do
1731		# Reset the window change trap notify.
1732		winch=
1733
1734		# Get the number of encrypted images/devices
1735		encrypted=$(($(/bin/ls $geli_images 2>&3 | /usr/bin/wc -l)))
1736
1737		# Draw the heading.
1738		monitorHeading ${list:-0} ${mounts:-0} $encrypted ${keys:-0}
1739
1740		test -e "$lock" && continue
1741
1742		# The list of managed nodes.
1743		list=$(/bin/cat $nodes 2>&3 | /usr/bin/sort -t\; -k2)
1744		# The 'real' mountdir.
1745		dir=$(realpath $mountdir 2>&3 || echo $mountdir)
1746		# The list of available keys.
1747		keys=$(/bin/cat $geli_availablekeys 2>&3)
1748		# The list of mounted labels.
1749		mounts=$(/sbin/mount | /usr/bin/grep -F " $dir")
1750		# The list of encrypted, unlocked images/devices.
1751		gnodes=$(/bin/cat $geli_nodes 2>&3)
1752
1753		# Print the list of labels.
1754		test -n "$list" \
1755		&& monitorListHeading LABEL DEVICE KEYS MODE \
1756		&& for label in $list; do
1757			device="${label%%;*}"
1758			label="${label#*;}"
1759			pretty="$(urldecode "$label")"
1760
1761			# Get the number of keys.
1762			nkeys=$(($(echo "$keys" | /usr/bin/grep -F "$label;" | /usr/bin/wc -l)))
1763			test 0 -eq $nkeys && nkeys=-
1764
1765			# Is the fs mounted and if so in ro or rw mode?
1766			mode=-
1767			mount=$(echo "$mounts" | /usr/bin/grep -F " $dir/$label") \
1768			&& mode=rw
1769			echo $mount | /usr/bin/grep -qF ', read-only' && mode=ro
1770
1771			monitorListRow $pretty $device $nkeys $mode || break
1772		done
1773		# Calculate the number of labels.
1774		list=${list:+$(($(echo "$list" | /usr/bin/wc -l)))}
1775		list=${list:-0}
1776		# Calculate the number of mounted labels.
1777		mounts=${mounts:+$(($(echo "$mounts" | /usr/bin/wc -l)))}
1778		mounts=${mounts:-0}
1779
1780		# Print the list of encrypted images/devices.
1781		test -n "$(/bin/ls $geli_images 2>&3)" \
1782		&& monitorListHeading ENCRYPTED DEVICE \
1783		&& for image in $(/bin/ls $geli_images 2>&3); do
1784			label=$(echo $gnodes | /usr/bin/grep "^$image;")
1785			label=${label#*;}
1786			device=${label%;*}
1787			label=${label#*;}
1788
1789			monitorListRow $image $device || break
1790		done
1791
1792		# Print the list of available keys.
1793		test -n "$keys" \
1794		&& monitorListHeading KEY ORIGIN \
1795		&& for key in $keys; do
1796			pretty=$(urldecode "${key%%;*}")
1797			key=${key#*;}
1798
1799			monitorListRow $key $pretty || break
1800		done
1801		# Calculate the number of available keys.
1802		keys=${keys:+$(($(echo "$keys" | /usr/bin/wc -l)))}
1803		keys=${keys:-0}
1804
1805		# Overwrite trailing rows.
1806		monitorListTail
1807
1808		# Skip idle interval if a window change trap was executed.
1809		test -n "$winch" && continue
1810
1811		# Sleep.
1812		monitorIdle
1813	done
1814}
1815
1816# Ensure the lock is aquired. And run the requested command.
1817case "$1" in
1818locked)
1819	case "$2" in
1820	start | update | stop | mount | umount)
1821		$2 "$3"
1822		return
1823		;;
1824	esac
1825	;;
1826list | mlist | monitor)
1827	$1 "$2"
1828	return
1829	;;
1830umount)
1831	# Unmounts should not happen during updates.
1832	exec /usr/bin/lockf -st 0 "$lock" $0 locked "$@"
1833	;;
1834mount)
1835	exec /usr/bin/lockf -st "$timeout" "$lock" $0 locked "$@"
1836	;;
1837devd)
1838	if devd; then
1839		shift
1840		# Execute in background.
1841		/usr/bin/lockf -st "$timeout" "$lock" $0 locked "$@" &
1842		return
1843	fi
1844	;;
1845start | update | stop)
1846	/usr/bin/lockf -st "$timeout" "$lock" $0 locked "$@"
1847	status=$?
1848	case $status in
1849	$EX_USAGE | $EX_SOFTWARE | $EX_OSERR | $EX_CANTCREAT | $EX_TEMPFAIL)
1850		echo "automounter: Lock could not be aquired:" "$@" 1>&2
1851		;;
1852	esac
1853	return $status
1854	;;
1855?*)
1856	echo "automounter: unknown directive '$1'." 1>&2
1857	;&
1858'')
1859	/bin/cat << HERE-DOC
1860Usage:	automounter (start | update | list | mlist | monitor | stop)
1861	automounter list [mounted | labels | keys | encrypted]
1862	automounter mlist [mounted | llinks | dlinks]
1863	automounter monitor [interval]
1864HERE-DOC
1865	return $ERR_CMD_UNKNOWN
1866	;;
1867esac
1868
1869