#!/bin/bash -e
# Copyright (c) TurnKey GNU/Linux - http://www.turnkeylinux.org
#
# This file is part of Deck
#
# Deck is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.

fatal() { echo "fatal: $@" 1>&2; exit 1; }

usage() {
cat<<EOF
Deck a filesystem using overlayfs (experimental)
Syntax: $(basename $0) path/to/dir/or/deck path/to/new/deck
Syntax: $(basename $0) [ -option ] path/to/existing/deck

Options:
    -m|--mount      - mounts a deck (the default)
    -u|--umount     - unmount a deck
    -D|--delete     - delete a deck

    --isdeck        - test if path is a deck
    --isdirty       - test if deck is dirty
    --ismounted     - test if deck is mounted
    --list-layers   - list layers of a deck

EOF
exit 1
}

deck_mount() {
    local parent=$(realpath $1)
    local mountpath=$(realpath $2)
    [ -d $parent ] || fatal "parent does not exist"
    
    if [ -d $mountpath ]; then
        [ "$(find $mountpath -maxdepth 0 -empty)" ] || \
            fatal "mountpath exists but not empty"
    fi

    local deckdir=$(dirname $mountpath)/.deck/$(basename $mountpath)
    local workdir=$deckdir/work
    local upperdir=$deckdir/upper
    mkdir -p $deckdir $workdir $upperdir

    if deck_isdeck $parent; then
        echo $parent > $deckdir/parent
        local parent_deckdir=$(dirname $parent)/.deck/$(basename $parent)
        cp $parent_deckdir/layers $deckdir/layers
        echo $parent_deckdir/upper >> $deckdir/layers
        deck_ismounted $parent && deck_umount $parent
    else
        echo $parent > $deckdir/parent
        echo $parent > $deckdir/layers
    fi

    mkdir -p $mountpath
    local lowerdir=$(cat $deckdir/layers | tr '\n' ':' | sed 's/:$//')
    local options="lowerdir=$lowerdir,upperdir=$upperdir,workdir=$workdir"
    mount -t overlay overlay -o $options $mountpath
}

deck_remount() {
    local mountpath=$(realpath $1)
    [ -d $mountpath ] || fatal "mountpath does not exists"
    deck_ismounted $mountpath && fatal "already mounted"
    deck_isdeck $mountpath || fatal "not a deck"
    local deckdir=$(dirname $mountpath)/.deck/$(basename $mountpath)
    local parent=$(cat $deckdir/parent)
    deck_mount $parent $mountpath
}

deck_umount() {
    local mountpath=$(realpath $1)
    [ -d $mountpath ] || fatal "mountpath does not exist"
    deck_ismounted $mountpath || fatal "not mounted"
    umount $mountpath
}

deck_delete() {
    local mountpath=$(realpath $1)
    local deckdir=$(dirname $mountpath)/.deck/$(basename $mountpath)
    [ -d $mountpath ] || fatal "mountpath does not exists"
    $(cat $(dirname $deckdir)/*/parent | grep -q "^${mountpath}$") && \
        fatal "cannot delete a parent deck"

    deck_ismounted $mountpath && umount $mountpath
    deck_isdeck $mountpath && rm -rf $deckdir
    rmdir $mountpath
    rmdir --ignore-fail-on-non-empty $(dirname $deckdir)
}

deck_isdirty() {
    local mountpath=$(realpath $1)
    local deckdir=$(dirname $mountpath)/.deck/$(basename $mountpath)
    [ "$(find $deckdir/upper -maxdepth 0 -empty)" ] && return 1
    return 0
}

deck_isdeck() {
    local mountpath=$(realpath $1)
    local deckdir=$(dirname $mountpath)/.deck/$(basename $mountpath)
    [ -d $mountpath ] || return 1
    [ -d $deckdir ] || return 1
    [ -d $deckdir/work ] || return 255
    [ -d $deckdir/upper ] || return 255
    [ -e $deckdir/layers ] || return 255
    [ -e $deckdir/parent ] || return 255
    return 0
}

deck_ismounted() {
    local mountpath=$(realpath $1)
    [ -d $mountpath ] || fatal "mountpath does not exist"
    return $(mount |grep -q "^overlay on $mountpath type overlay")
}


opts=()
args=()
while [ "$1" != "" ]; do
    case $1 in
        -h|--help)  usage;;
        -*)         opts=("${opts[@]}" "$1");;
        *)          args=("${args[@]}" "$1");;
    esac
    shift
done

[[ ${#args[@]} -eq 0 ]] && usage
[[ ${#args[@]} -gt 2 ]] && fatal "too many arguments";
[[ ${#opts[@]} -gt 1 ]] && fatal "conflicting deck options"
[[ ${#opts[@]} -eq 0 ]] && opts=("--mount")
action=${opts[0]}

case $action in
    -u|--umount|-D|--delete|--isdeck|--ismounted|--show-layers)
        [[ ${#args[@]} -eq 1 ]] || fatal "missing argument";;
esac

[ "$(id -u)" != "0" ] && fatal "must be run as root"
lsmod |grep -q overlay || fatal "overlay module not loaded"

case $action in
    -m|--mount)
        [[ ${#args[@]} -eq 2 ]] && deck_mount ${args[0]} ${args[1]};
        [[ ${#args[@]} -eq 1 ]] && deck_remount ${args[0]};
        ;;
    -u|--umount)
        deck_umount ${args[0]};
        ;;
    -D|--delete)
        deck_delete ${args[0]};
        ;;
    --isdeck)
        exit $(deck_isdeck ${args[0]});
        ;;
    --isdirty)
        exit $(deck_isdirty ${args[0]});
        ;;
    --ismounted)
        exit $(deck_ismounted ${args[0]});
        ;;
    --show-layers)
        deck_isdeck ${args[0]} || fatal "not deck";
        mountpath=$(realpath ${args[0]});
        deckdir=$(dirname $mountpath)/.deck/$(basename $mountpath);
        echo $deckdir/upper;
        tac $deckdir/layers;
        ;;
    *)
        fatal "unrecognized option: $action";
        ;;
esac

exit 0
