# vim: filetype=sh

# partitioning and formatting
# ---------------------------

# Selection of ext4 features:
# We must select only features available on the *oldest* system
# version we want to support. We can get these features by
# creating a sample filesystem on such a system:

# $ cd /tmp
# $ dd of=test.ext4 bs=1G count=0 seek=100
# $ mkfs.ext4 -F -q -L ROOT -T big -m 2 test.ext4
# $ dumpe2fs test.ext4 | grep features

# Note about the '-T big' option:
# We try to create a USB stick as small as possible.
# However, the embedded system may later by copied on a
# potentially large disk.
# As a result, we should select appropriate ext4 features even if
# the filesystem might be considered 'small' at first.
# This may seem cosmetic but it's not: if initialized with
# '-T small' (or with no -T option and run on a small disk),
# when we move to a large disk, resize2fs apparently
# enables the 'meta_bg' option (supposedly trying to adapt as much
# as possible this 'small' filesystem to a much larger device).
# Since this option is not handled by grub, it prevents the
# system from booting properly.

EXT4_FEATURES=$(cat << EOF
has_journal ext_attr resize_inode dir_index filetype extent
flex_bg sparse_super large_file huge_file uninit_bg dir_nlink
extra_isize
EOF
)

format_fat()
{
    label="$1"
    device="$2"
    label_option=""
    if [ ! -z "$label" ]
    then
        label_option="-n $label"
    fi
    quiet mkfs.vfat $label_option "$device"
}

format_ext4()
{
    label="$1"
    device="$2"
    label_option=""
    if [ ! -z "$label" ]
    then
        label_option="-L $label"
    fi
    features="$(echo $EXT4_FEATURES | tr ' ' ',')"
    mkfs.ext4 -F -q $label_option -b 4096 -O "none,$features" -m 2 "$device"
}

get_partition_devices()
{
    image_device="$1"
    kpartx -l $image_device | awk '{ print "/dev/mapper/"$1 }'
}

partition_image()
{
    image_path="$1"
    layout_dir="$2"

    {
        part_table_type=$(cat "$layout_dir"/part_table_type)
        echo "label: $part_table_type"
        echo
        for vol in $(ls -1d "$layout_dir"/partitions/* | sort -n)
        do
            vol_id="$(basename $vol)"
            applied_size_mb=$(cat $vol/applied_size_mb)
            fdisk_type=$(cat $vol/fdisk_type)
            partdef="type=$fdisk_type"
            if [ $applied_size_mb -gt 0 ]
            then
                partdef="size=${applied_size_mb}MiB,$partdef"
            fi
            echo $partdef
        done
    } | sfdisk "$image_path" >/dev/null
}

save_vol_uuid()
{
    vol_dir="$1"
    blkid -s UUID -o value $vol_dir/device > $vol_dir/uuid
}

format_volume()
{
    lvm_vg="$1"
    vol_dir="$2"
    vol_subtype=$(cat "$vol_dir/subtype")

    label=""
    if [ -f "$vol_dir/label" ]
    then
        label="$(cat "$vol_dir/label")"
    fi

    case $vol_subtype in
        efi|fat)
            format_fat "$label" $vol_dir/device
            echo vfat > $vol_dir/mounttype
            save_vol_uuid $vol_dir
            ;;
        ext4)
            format_ext4 "$label" $vol_dir/device
            echo ext4 > $vol_dir/mounttype
            save_vol_uuid $vol_dir
            ;;
        lvm)
            # this is our LVM PV partition
            quiet pvcreate $vol_dir/device
            # we have to set the physical extent size to 1M otherwise
            # default value may be above (4M) and cause issues with
            # our volume size calculations
            quiet vgcreate -s 1M $lvm_vg $vol_dir/device
            ;;
    esac
}

create_lvm_volume()
{
    lvm_vg="$1"
    vol_dir="$2"

    label=$(cat "$vol_dir/label")
    applied_size_mb=$(cat "$vol_dir/applied_size_mb")

    if [ "$applied_size_mb" -eq "0" ]
    then
        quiet lvcreate -n "$label" -l 100%FREE "$lvm_vg"
    else
        quiet lvcreate -n "$label" -L ${applied_size_mb}M "$lvm_vg"
    fi
    ln -sf "/dev/$lvm_vg/$label" "$vol_dir/device"
}

format_volumes()
{
    work_dir="$1"
    layout_dir="$2"
    lvm_vg="$3"
    device="$(readlink $work_dir/device)"

    # retrieve the partition devices
    partition_devices="$(get_partition_devices $device)"

    # format partitions
    i=1
    for part_device in $(echo "$partition_devices")
    do
        wait_for_device $part_device
        part_vol="$layout_dir/partitions/$i"
        ln -sf "$part_device" "$part_vol/device"
        format_volume $lvm_vg $part_vol
        i=$((i+1))
    done

    # create and format lvm volumes
    for lvm_vol_id in $(ls -1 "$layout_dir"/lvm_volumes/ | sort -n)
    do
        lvm_vol="$layout_dir"/lvm_volumes/$lvm_vol_id
        create_lvm_volume $lvm_vg $lvm_vol
        format_volume $lvm_vg $lvm_vol
    done
}

generate_mount_info()
{
    layout_dir="$1"
    for vol_dir in $(ls -d "$layout_dir"/*/*)
    do
        echo "$(cat $vol_dir/mountpoint) $vol_dir"
    done | sort -k 1,1 | while read vol_mountpoint vol_dir
    do
        if [ "$vol_mountpoint" = 'none' ]
        then
            continue
        fi
        echo "$vol_dir" "$(cat $vol_dir/mounttype)" "$(cat $vol_dir/uuid)" "$vol_mountpoint"
    done
}

mount_volumes()
{
    work_dir="$1"
    layout_dir="$2"
    fs_dir="$1/fs"

    generate_mount_info "$layout_dir" | while read vol_dir mounttype uuid vol_mountpoint
    do
        if [ "$vol_mountpoint" = "/" ]
        then
            fs_mountpoint="$fs_dir"
        else
            fs_mountpoint="$fs_dir$vol_mountpoint"
        fi
        mkdir -p "$fs_mountpoint"
        failsafe mount $vol_dir/device "$fs_mountpoint"
        echo "undo mount $vol_dir/device \"$fs_mountpoint\"" >> $work_dir/release_info
        # if this is the rootfs volume, save a pointer to it
        if [ "$vol_mountpoint" = "/" ]
        then
            ln -s $vol_dir $work_dir/rootfs_vol
        fi
    done
}

create_formatted_image()
{
    image_name=$1   # 'draft' or 'final'
    target_fs="$2"
    stick_os_id=$3
    image_file="$4"
    work_dir="$DBSTCK_TMPDIR/$image_name"
    layout_dir="$DBSTCK_TMPDIR/.layout"
    if [ -z "$image_file" ]
    then
        image_file="$work_dir/file"
    fi

    mkdir -p "$work_dir"

    # compute vg name for future use
    lvm_vg=$(get_vg_name $image_name $stick_os_id)
    echo $lvm_vg > "$work_dir/vg_name"

    # compute size of partitions and lvm volumes
    compute_applied_sizes $image_name $layout_dir "$target_fs"

    # create image file
    rm -f "$image_file"
    stick_size_mb=$(cat $layout_dir/needed_size_mb)
    $DD bs=$((1024*1024)) seek=$stick_size_mb count=0 of="$image_file"

    # create partitions
    partition_image "$image_file" "$layout_dir"

    # let the kernel know about this device
    image_device=$(losetup -f)
    failsafe losetup $image_device "$image_file"
    echo "undo losetup $image_device \"$image_file\"" >> $work_dir/release_info
    ln -sf "$image_device" "$work_dir/device"
    failsafe kpartx -a $image_device
    echo "undo kpartx -a $image_device" >> $work_dir/release_info

    # format partitions and lvm volumes, then mount them
    format_volumes "$work_dir" "$layout_dir" "$lvm_vg"
    mount_volumes "$work_dir" "$layout_dir"
}

release_image()
{
    image_name="$1"     # 'draft' or 'final'
    work_dir="$DBSTCK_TMPDIR/$image_name"

    eval "$(tac $work_dir/release_info)"
    rm $work_dir/file
}

