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