#!/bin/sh
### mklv.sh  -*- Sh -*-
## Start a new LVM logical volume, idiosyncratically.

### Ivan Shmakov, 2020

## To the extent possible under law, the author(s) have dedicated
## all copyright and related and neighboring rights to this software
## to the public domain worldwide.  This software is distributed
## without any warranty.

## You should have received a copy of the CC0 Public Domain Dedication
## along with this software.  If not, see
## <http://creativecommons.org/publicdomain/zero/1.0/>.

### Examples:

##    # mklv  /dev/pverha:5120-8192  vgskyddy-iv/lvdownload,ext4,4480M \
##          /dev/vgskiddy-iv/lvmisc-z5fde03,snapshot,64M \
##          /dev/vgskyddy-iv/lvpool-z5fde2f \
##          vgskyddy-iv/lvrheyla-root-z5fde32,0,1536M \
##          vgskyddy-iv/lvrheyla-var-z5fde32,0,512M \
##          /dev/vgskyddy-iv/lvrheyla-home-z5fde16-x5fde1bcd.thin 

### History:

## 0.4  2020-12-20 18:50Z
##      (lv_arg): Fixed: in the two cases remaining from the previous
##      change, use argument value up to but excluding the first comma
##      as the possible logical volume name.

## 0.3  2020-12-20 17:55:50Z
##      (sfn.x3YKqUcE_xnktR4aEXy2_btGp_reRaMvi-ZkjS2iiWw.sh)
##      Consider an argument to refer to a physical volume if it
##      consists of an existing block device name, a colon, and
##      an extent range.  Require that said range contains a dash
##      and do not query LVM if these criteria are met.  Fixed:
##      report argument in full when emitting an all-zero check
##      debugging message (were: trying to remove an unlikely suffix
##      starting with a colon.)
##      (pv_arg): New helper function.
##      (lv_arg): Fixed: use argument value up to but excluding the
##      first comma as the possible logical volume name (was: up to
##      but excluding the last colon, though any colons are unlikely
##      in this branch), thus now allowing for ,snapshot and the like.

## 0.2  2020-12-19 20:02:17Z
##      (sfn.oKki5eN_WgMyI0iDcvs8rSUanQDyGLGt5u1dqm2ShxU.sh)
##      Fixed vg= determination (was: having a trailing /.)
##      (lv_arg): Fixed: obtain origin thin volume block device filename
##      from $1 (was: $f.)

## 0.1  2020-12-19 19:25:42Z
##      (sfn.muKFLMPKF20EOMMD900qJW9Y8ODRvMbED5XkZnPR-Ek.sh)
##      Initial revision.

### Code:

set -e
set -C -u

: "${MKLV_DEFAULT_SIZE:=2240M}"
: "${MKLV_DEFAULT_SNAPSHOT_SIZE:=64M}"

blk_z () {
    ## .
    blockdev --getsize64  /dev/stdin
}

gerr () {
    gerr_exit=${1}
    shift
    printf "$@" >&2

    ## .
    test "$gerr_exit" = 0 \
        || exit "$gerr_exit"
}

nth () {
    shift "$1"
    ## .
    test "$#" -ge 2 \
         && printf %s\\n "$2"
}

layout () {
    case "$2" in
        ("$1" | *,"$1" | "$1",* | *,"$1",*) ;;
        (*) return 1 ;;
    esac
}

pv=
pvn=
pool_vg=
pool_lv=
pool_n=

pv_pool_new_1 () {
    local e=
    if test "$pool_n" = 0 ; then
        e=1
        gerr 0 "Error: %s: Pool (%s) specified previously was never used?" \
            "${1}" "${pool_vg}/${pool_lv}"
    fi
    if test "$pvn" = 0 ; then
        e=1
        gerr 0 "Error: %s: PV (%s) specified previously was never used?" \
            "${1}" "$pv"
    fi
    ## .
    test -z "$e" \
        || exit "$e"
}

pv_arg () {
    pv_pool_new_1 "$1"
    pool_vg=
    pool_lv=
    pool_n=
    pv=${1}
    pvn=0
}

## FIXME: below we should probably try to query LVM once and cache results

lv_info=
layout=
orig_vg=
orig_lv=
lv_arg () {
    local f
    f=${1%%,*}
    ## NB: sets global lv_info and possibly layout variables
    if  ! lv_info=$(lvs --noheading \
                        -o lv_layout,vg_name,lv_name \
                        --unbuffered --unit=b --nosuffix -- "$f") \
            || ! layout=$(nth 0 ${lv_info}) ; then
        gerr 0 "D: %s: Not recognized as an (existing) logical volume\\n" \
            "$f"
        ## .
        return 1
    fi

    local lv
    lv=$(nth 2 ${lv_info})
    if layout thin "$layout" ; then
        gerr 0 "I: %s: Creating a .new thin snapshot for this LV\\n" \
            "$1"
        local ro
        if  ! ro=$(blockdev --getro  /dev/stdin < "$f") \
                || ! test "$ro" = 1 ; then
            gerr 0 "I: %s: Is NOT a read-only block device\\n" \
                "$f"
        fi
        ## NB: one of the only three lvcreate invocations in this code
        ## FIXME: not future-proof: will only work up to 2106
        lvcreate -Ka y --snapshot -n "${lv%-x[0-9a-f]???????.thin}".new \
            -- "$f"
        ## .
        return 0
    fi

    local vg
    vg=$(nth 1 ${lv_info})
    if ! layout pool "$layout" ; then
        orig_vg=${vg}
        orig_lv=${lv}
        ## .
        return 1
    fi

    pv_pool_new_1 "$1"
    pv=
    pvn=
    pool_vg=${vg}
    pool_lv=${lv}
    pool_n=0
}

for x ; do
    orig_vg=
    orig_lv=
    p1=${x%:*-*}
    ra=${x#${p1}}
    if test -n "$ra" && test -b "$p1" ; then
        gerr 0 "D: %s: Extent range (:%s) given for a block device; assuming PV\\n" \
            "$p1" "$ra"
        pv_arg "$x"
    elif lv_arg "$x" ; then
        continue
    elif [ -b "${x%:*-*}" ] ; then
        gerr 0 "D: %s: Block device not recognized as a logical volume; assuming PV\\n" \
            "$p1"
        pv_arg "$x"
        continue
    fi

    ## FIXME: handle multiple /s (like: //dev///vgerha/lvdajya)
    y=${x#/dev/}
    lv1=${y##*/}
    if test -z "${orig_vg}${orig_lv}" ; then
        vg=${y%/${lv1}}
        lv=${lv1%%,*}
    else
        vg=${orig_vg}
        lv=${orig_lv}-x$(printf %08x\\n "$(date +%s)").new
    fi

    ## FIXME: not future-proof: will only work up to 2106
    case "$lv" in
        (*.new | *-z[0-9a-f]????? | *-z[0-9a-f]?????.new) ;;
        (*-x[0-9a-f]??????? | *-z[0-9a-f]???????.new) ;;
        (*) lv=${lv}-z$(printf %06x\\n $(($(date +%s) / 0x100))).new ;;
    esac

    case "$lv1" in
        (*,*,*)
            u1=${lv1#*,}
            u=${u1%%,*}
            s=${u1#*,} ;;
        (*,*) u= ; s=${lv1##*,} ;;
        (*)   u= ; s= ;;
    esac

    g=
    if test -z "$u" ; then
        : do nothing
    elif test -z "$orig_lv" && test "$u" = snapshot ; then
        gerr 1 "Error: %s: Snapshot option given for a non-LV" \
            "$x"
    elif test -n "$orig_lv" && test "$u" != snapshot ; then
        gerr 1 "Error: %s (%s): Unrecognized snapshot option" \
            "$x" "$u"
    else
        case "$u" in
            (????????-????-????-????-????????????)
                g=/dev/disk/by-uuid/${u}
                if test -e "$g" ; then
                    gerr 1 "Error: %s (%s): UUID already used (%s)" \
                        "$x" "$u" "$g"
                fi
                ;;
        esac
    fi

    if test -z "$s" ; then
        if test -n "$orig_lv" || test "$u" = snapshot ; then
            s=${MKLV_DEFAULT_SNAPSHOT_SIZE}
        else
            s=${MKLV_DEFAULT_SIZE}
        fi
    fi

    ## NB: two of the only three lvcreate invocations in this code
    f=/dev/${vg}/${lv}
    if test -n "$pool_lv" ; then
        lvcreate --zero=n  --thinpool="$pool_lv" -V "$s" -n "$lv" -- "$vg"
    else
        lvcreate --zero=n   -C y   -L "$s" -n "$lv" -- "$vg" ${pv:+"$pv"}
        test -n "$pvn" && pvn=$((1 + pvn))
        ## NB: newly created thin volumes are expected to be all-zero by design
        gerr 0 "D: %s: Will now check if the device is all-zero\\n" \
            "$x"
        if ! cmp -n "$(blk_z < "$f")" -- /dev/zero "$f" ; then
            gerr 1 "Error: %s: Stray data; use shred -zn0, lvremove and rerun?" \
                "$x"
        fi
    fi

    eopts=resize=$((5 * ${s%[!0-9]} / 2))${s##*[0-9]}
    # mke2fs -m 2 -j ${u:+-U "$u"} -O extent,dir_nlink,metadata_csum,^ext_attr -I 128 -E "$eopts" -- "$f" 
    if test "$u" = 0 ; then
        : do nothing
    elif test -n "$orig_lv" ; then
        lvconvert --snapshot -- "${orig_vg}/${orig_lv}" "$f"
        lvchange -p r -- "$f"
    else
        ## NB: ${u} designates either UUID (-U) or mke2fs.conf(5) FS type (-t)
        mke2fs ${g:+-U}${g:--t} "$u" -E "$eopts" -- "$f"
    fi
    case "${lv1%%,*}" in
        (*.new) ;;
        (*) lvrename -- "$f" "${f%.new}" ;;
    esac
done

## Check for inconsistent usage one last time
pv_pool_new_1 "(end-of-code)"

### mklv.sh ends here
