#!/bin/bash

# This script umount a USB storage device from the filesystem space.
# The arguments are either the path under which the key is mounted
# or the path to the device node. All mounts of the device are removed,
# if a mount path is passed as argument all mounts of the device mounted
# at that location are removed.

#
# The possible exit codes
#

# Unmounted OK
EXIT_OK=0
# Gereral error, did not try to unmount
EXIT_ERROR=1
# Filesystem is busy, not unmounted
EXIT_BUSY=2
# Filesystem busy and -f passed: used as lazy unmount
# need to unplug and replug the USB storage device.
EXIT_LAZY=3

# Maximum number of tries to grab the fstab lock
MAX_LOCK_TRIES=5

# Maximum time to wait trying to unmount a device
MAX_UMOUNT_WAIT=5

# Logs a message to syslog and stdout, the arguments are the message
log() {
    # get input from /dev/null to prevent logger from reading from stdin
    # and blocking if there are no arguments
    logger -t "usb-remove[$$]" -- "$@" < /dev/null
    echo "$@"
}

#
# Grab lock
#
# NOTE: we use the same lock as media-mount and other fstab manipulating
# utilities so that there are no concurrent mounts / umounts and no
# concurrent invocations of this script.

fstab_lock() {
    for (( i=1; i <= $MAX_LOCK_TRIES; i++ )) ; do
        dotlockfile -p -r 0 -l /var/run/fstab.lock && return 0
        sleep 1
    done
    log "ERROR: timed out acquiring fstab lock, system busy, try again later" >&2
    return 1
}

fstab_unlock() {
    dotlockfile -p -u /var/run/fstab.lock
}

# returns the mount locations for a device
get_mounts_for_dev() {
    grep "^$1[[:space:]]" /proc/mounts | cut -d' ' -f 2
}

# returns the device mounted last at the given location
get_dev_for_mount() {
    grep "^[^[:space:]]\+[[:space:]]\+${1%/}[[:space:]]" /proc/mounts | cut -d' ' -f 1 | tail -n 1
}

fstab_lock || exit $EXIT_ERROR

trap fstab_unlock EXIT

#
# Detect force mode
#
FORCE=no
if [ "$1" = "-f" ]; then
    FORCE=yes
    shift
fi

#
# Find mountpoint
#
if [ -b "$1" ]; then
    DEV="$1"
elif [ -d "$1" ]; then
    m="$1"
    mr="$(readlink -ms "$m")"
    if [ $? -ne 0 ]; then
        log "ERROR: cannot resolve '$m' for symlinks" >&2
        exit $EXIT_ERROR
    fi
    DEV="$(get_dev_for_mount "$mr")"
    if [ -z "$DEV" ]; then
        log "ERROR: no mounts at '$m' (resolved to '$mr')" >&2
        exit $EXIT_ERROR
    fi
else
    log "ERROR: '$1' is not a mount point path nor a device node" >&2
    exit $EXIT_ERROR
fi

MPTS="$(get_mounts_for_dev "$DEV" | tac)"
if [ -z "$MPTS" ]; then
    log "ERROR: device '$DEV' is not mounted" >&2
    exit $EXIT_ERROR
fi

#
# Unmount
#

RET=$EXIT_OK
for m in $MPTS; do
    r=1
    for (( i=1; i<="$MAX_UMOUNT_WAIT"; i++)); do
        umount "$m" 2>/dev/null
        if [ $? -eq 0 ]; then
            r=0
            break
        fi
        sleep 1
    done
    [ "$r" -eq 0 ] && continue

    if [ "$FORCE" != yes ]; then
        log "ERROR: filesystem busy at '$m', cannot unmount" >&2
        exit $EXIT_BUSY
    else
        umount -l "$m"
        if [ $? -eq 0 ]; then
            RET=$EXIT_LAZY
        else
            log "ERROR: failed forced unmounting at '$m'" >&2
            exit $EXIT_BUSY
        fi
    fi
done


exit $RET
