#!/bin/bash

# This script copies the desired information file to
# the failsafe data partition on the MTD device (flash memory).
#
# The arguments can be any of the following
# copy <dest> <source>
# set <dest> <content>
# remove <dest>
# where <dest> is the desination path in the failsafe data
# partition. The copy action copies the <source> file to
# <dest>, the set action outputs the <content> string to
# <dest> and the remove action removes <dest>.
#
# The exit status is
# 0 = success
# 1 = general error
# 2 = MTD device not found
# 3 = timeout waiting to obtain lock on MTD device
#
# When it is certain that no modifications are done the MTD is
# left unmodified, instead of erasing and rewriting the same data.

# The maximum wait for the MTD device to become free (seconds)
MAX_LOCK_WAIT=60

# The maximum wait for the MTD block device to appear (seconds)
# (at boot this can take long as udev might be busy timing out on other nodes)
MAX_BLOCKDEV_WAIT=30

inithwinfo() {
    hwtype=
    while read tag sep val; do
	if [ "$tag" = "Hardware" ]; then
	    hwtype="$val"
	    break;
	fi
    done < /proc/cpuinfo

    case "$hwtype" in
	Sakura)
	    FFSTYPE=jffs2
	    PAGESIZE=2048
	    BLOCKSIZE=131072
	    MTDTYPE=nand
	    ;;
	Bonsai)
	    FFSTYPE=cramfs
	    MTDTYPE=nor
	    ;;
    esac

    return 0
}

function get_mtd() {
    mtdnr="$(sed -ne '/\"failsafe-data\"$/{s/mtd\([0-9]\+\).*/\1/;p}' /proc/mtd)" || return 1
    mtdblock="/dev/mtdblock$mtdnr"
    mtddev="/dev/mtd$mtdnr"
    [ -c "$mtddev" ] || return 1
    [ -b "$mtdblock" ] || modprobe mtdblock || return 1
    return 0
}

function lock() {
    i=0
    while ! lockdev -l "$mtddev"; do
	if [ "$i" -ge $MAX_LOCK_WAIT ]; then
	    return 1
	fi
	(( i++ ))
	sleep 1
    done
    return 0
}

function unlock() {
    lockdev -u "$mtddev"
}

function files_differ() {
    [ ! -f "$1" -o ! -f "$2" ] && return
    # as cmp is not available we use sha1sum to compare
    [ "$(cat "$1" | sha1sum)" != "$(cat "$2" | sha1sum)" ]
}

function set_data() {
    # Compute paths and create temp sub-dirs
    tmpmount="$tmpdir/mnt"
    tmpdatadir="$tmpdir/data"
    tmpfsimg="$tmpdir/fsimg"

    mkdir -m 700 "$tmpmount" || return 1
    mkdir -m 700 "$tmpdatadir" || return 1

    # if the mtblock module was just loaded it may take some time for
    # the device node to appear, wait for it
    i=0
    while ! [ -e "$mtdblock" ]; do
	if [ "$i" -ge $MAX_BLOCKDEV_WAIT ]; then
	    return 1
	fi
	(( i++ ))
	sleep 1
    done

    # Mount filesystem and copy data over to temp dir if present
    mount -t "$FFSTYPE" -r "$mtdblock" "$tmpmount" 2>/dev/null
    if [ $? -eq 0 ]; then
	cp -a "$tmpmount/." "$tmpdatadir"
	r=$?
	umount "$tmpmount"
	[ $r -eq 0 ] || return 1
    fi

    # perform the specified operations
    changed=false
    while [ "$#" -ne 0 ]; do
	case "$1" in
	    "copy")
		if files_differ "$3" "$tmpdatadir"/"$2" ; then
		    cp -f -- "$3" "$tmpdatadir"/"$2" || return 1
		    changed=true
		fi
		shift 3
		;;
	    "set")
		echo -n "$3" > "$tmpdatadir"/"$2" || return 1
		changed=true
		shift 3
		;;
	    "remove")
		if [ -e "$tmpdatadir"/"$2" ]; then
		    rm -f "$tmpdatadir"/"$2" || return 1
		    changed=true
		fi
		shift 2
		;;
	    *)
		echo "unknown action '$1'" >&2
		return 1
	esac
    done

    # do not re-make the filesystem if nothing changed
    [ "$changed" = "false" ] && return 0

    # make filesystem again
    case "$FFSTYPE" in
	cramfs)
	    mkcramfs "$tmpdatadir" "$tmpfsimg" || return 1
	    ;;
	jffs2)
	    mkfs.jffs2 -n -s "$PAGESIZE" -e "$BLOCKSIZE" \
		-d "$tmpdatadir" -o "$tmpfsimg" || return 1
	    ;;
	*)
	    echo "unknown filesystem type" >&2
	    return 1
    esac

    # save filesystem to MTD
    if [ "$MTDTYPE" = "nand" ]; then
	flash_eraseall -q "$mtddev" || return 1
	nandwrite -p -q "$mtddev" "$tmpfsimg"
    else
	flashcp "$tmpfsimg" "$mtddev" || return 1
    fi

    return 0
}

function safe_data() {
    # Identify the MTD device and lock it
    get_mtd || return 2
    lock || return 3

    # Do all operations under tmpdir
    tmpdir="$(mktemp -d -t failsafe-data.XXXXXX)"
    if [ $? -ne 0 ]; then
	unlock
	return 1
    fi

    # Set the data
    set_data "$@"
    RET=$?

    # Cleanup all temporary files and unlock the device
    rm -rf "$tmpdir"
    unlock

    return $RET
}

if [ "$#" -eq 0 ]; then
    echo "usage: $0 {copy <dest> <source>|set <dest> <content>|remove <dest>} ..." >&2
    exit 1
fi

inithwinfo || exit 1
safe_data "$@"
