#!/bin/sh

#
# Script to format a storage device (e.g. partition on USB stick) for
# writable content storage. The argument is the sysfs block device
# (e.g., /sys/block/sda/sda1), if preceded by "-f" then the device
# is formatted even if contains some contents.
#

# Exit codes:

# Success
EXIT_OK=0
# General error, did not format
EXIT_ERROR=1
# Read only (write protected) device, did not format
EXIT_RODEV=2
# Contains content that may be in use, did not format, use -f to force
EXIT_BUSY=3
# The lock could not be obtained (normally a timeout)
EXIT_NOLOCK=4
# Format started but did not complete, filesystem corrupted
EXIT_FORMAT_FAILED=8
# In addition could not properly unmount the filesystem, filesystem corrupted
EXIT_FORMAT_FAILED2=9

# The label to use for the created filesystem
LABEL=hmp-content

# The maximum number of tries for /etc/fstab locking
MAX_LOCK_TRIES=5

# The maximum wait for umounting a filesystem
MAX_UMOUNT_WAIT=5

# The content directory and file to check for empty filesystem if mounted
CONTENTDIR=/srv/raperca/content
CONTENTINDEX="$CONTENTDIR"/index.svg

# Force formatting
FORCE=no

# 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 "content-format[$$]" -- "$@" < /dev/null
    echo "$@"
}

#
# Use this function instead of exit, so that the
# exit code may be logged
#
vexit() {
    local c="$?"
    [ -n "$1" ] && c="$1"
    log "EXIT: $c" >&2
    exit "$c"
}

#
# Parse arguments
#

if [ "$1" = "-f" ]; then
    FORCE=yes
    shift
fi
DEVPATH="$1"

if [ -z "$DEVPATH" ]; then
    log "ERROR: no device path given in command line" >&2
    vexit $EXIT_ERROR
fi

if [ ! -d "$DEVPATH" ]; then
    log "ERROR: '$DEVPATH' is not a device path" >&2
    vexit $EXIT_ERROR
fi

#
# 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 /etc/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 /etc/fstab.lock
}

fstab_lock || vexit $EXIT_NOLOCK

trap fstab_unlock EXIT

#
# Find device name
#

UDEVPATH="${DEVPATH#/sys}"
DEV="$(udevinfo -q name -p "$UDEVPATH")"
if [ $? -ne 0 -o -z "$DEV" ]; then
    log "ERROR: no device file for '$DEVPATH'" >&2
    vexit $EXIT_ERROR
fi
DEV=/dev/"$DEV"
if [ "$(blockdev --getro "$DEV")" != 0 ]; then
    log "ERROR: device is write-protected" >&2
    vexit $EXIT_RODEV
fi

#
# Umount device
#
umount_dev() {
    # Find all the device names and/or symlinks
    local DEVS="$(udevinfo -q all -p "$UDEVPATH" | awk '/^[NS]:/ {print $2}')"

    # Find all the mountpoint of the device, under any name, we gather
    # them in reverse order into $MPTS
    local MPTS=
    for d in $DEVS; do
	m="$(grep "^/dev/$d[[:space:]]" /proc/mounts | cut -d' ' -f 2 | tac | tr '\n' ' ')"
	if [ -n "$m" ]; then
	    MPTS="$m $MPTS"
	fi
    done

    if [ -z "$MPTS" ]; then
	return $EXIT_OK
    fi

    # Check for content
    for m in $MPTS; do
	if [ "$FORCE" != yes -a "$m" = "$CONTENTDIR" -a -f "$CONTENTINDEX" ]; then
	    log "ERROR: device has some contents that may be being played, aborting" >&2
	    return $EXIT_BUSY
	fi
    done

    # Umount all mount points
    local UMPTS=
    for m in $MPTS; do
	umount "$m" 2>/dev/null
	[ $? -eq 0 ] && UMPTS="$m $UMPTS" && continue

	log "INFO: mount point '$m' in use, waiting up to $MAX_UMOUNT_WAIT seconds" >&2
	# Mhh... filesystem in use, retry a few times
	r=1
	for (( i=0; i<="$MAX_UMOUNT_WAIT"; i++ )); do
	    umount "$m" 2>/dev/null
	    if [ $? -eq 0 ]; then
		log "INFO: device stopped" >&2
		r=0
	    fi
	    sleep 1
	done
	[ $r -eq 0 ] && UMPTS="$m $UMPTS" && continue
	log "ERROR: timed out trying to unmount '$DEVPATH' at '$m', filesystem busy, try again later" >&2
	[ -z "$UMPTS" ] || log "ERROR: already unmounted '$DEVPATH' from some locations (${UMPTS% }), cannot recover previous state" >&2
	return $EXIT_BUSY
    done

    return $EXIT_OK
}

umount_dev
RET=$?
if [ $RET -ne 0 ]; then
    log "ERROR: device is in use and could not stop it, exiting" >&2
    vexit $RET
fi

#
# Make the filesystem
#

# NOTE: mke2fs from e2fsprogs >= 1.36 reserves space for resizing but this
# filesystem will never be resized, so do not waste space on that

mke2fs -F -q -j -O dir_index,^resize_inode "$DEV"
if [ $? -ne 0 ]; then
    log "ERROR: failed creating ext3 filesystem on '$DEV'" >&2
    vexit $EXIT_FORMAT_FAILED
fi

#
# Adjust filesystem parameters
#

tune2fs -c 0 -i 0 -m 1 -L "$LABEL" "$DEV"
if [ $? -ne 0 ]; then
    log "ERROR: failed setting filesystem options on '$DEV'" >&2
    vexit $EXIT_FORMAT_FAILED
fi

#
# Create directories and adjust permissions in filesystem
#

mpt="$(mktemp -d /tmp/content-format.XXXXXX)"
if [ $? -ne 0 -o -z "$mpt" ]; then
    log "ERROR: failed creating temporary mountpoint" >&2
    vexit $EXIT_FORMAT_FAILED
fi

mount -t ext3 "$DEV" "$mpt"
if [ $? -ne 0 ]; then
    log "ERROR: failed temporary mount of new filesystem on '$DEV'" >&2
    rmdir "$mpt"
    vexit $EXIT_FORMAT_FAILED
fi

RET=$EXIT_OK

# Create the new style mount config file
touch "$mpt"/.spx-mount
if [ $? -ne 0 ]; then
    log "ERROR: failed creating the mount configuration file on new filesystem" >&2
    RET=$EXIT_FORMAT_FAILED
fi

mkdir "$mpt"/content
if [ $? -ne 0 ]; then
    log "ERROR: failed creating content directory on new filesystem" >&2
    RET=$EXIT_FORMAT_FAILED
fi

chgrp www "$mpt"/content
if [ $? -ne 0 ]; then
    log "ERROR: failed changing group on content directory of new filesystem" >&2
    RET=$EXIT_FORMAT_FAILED
fi

chmod g+w "$mpt"/content
if [ $? -ne 0 ]; then
    log "ERROR: failed setting group write permission on content directory of new filesystem" >&2
    RET=$EXIT_FORMAT_FAILED
fi

mkdir "$mpt"/capture
if [ $? -ne 0 ]; then
    log "ERROR: failed creating capture directory on new filesystem" >&2
    RET=$EXIT_FORMAT_FAILED
fi

chgrp raperca "$mpt"/capture
if [ $? -ne 0 ]; then
    log "ERROR: failed changing group on capture directory of new filesystem" >&2
    RET=$EXIT_FORMAT_FAILED
fi

chmod g+w "$mpt"/capture
if [ $? -ne 0 ]; then
    log "ERROR: failed setting group write permission on capture directory of new filesystem" >&2
    RET=$EXIT_FORMAT_FAILED
fi

umount "$mpt"
if [ $? -ne 0 ]; then
    umount -r "$mpt" 2>/dev/null; umount -l "$mpt" 2>/dev/null
    log "ERROR: failed unmounting new filesystem on '$DEV'" >&2
    RET=$EXIT_FORMAT_FAILED2
fi

rmdir "$mpt"
if [ $? -ne 0 ]; then
    log "WARNING: failed removing temporary mount directory" >&2
fi

vexit $RET
