#!/bin/ksh

#========================================
#
# usbfadm - USB Flash drive ADMinistration tool
# KAWAMATA, Yoshihiro / kaw@on.rim.or.jp
#
# $Id: usbfadm,v 1.130 2025/04/04 22:57:43 kaw Exp $
#
#========================================

# Copyright (c) 2005--2025
# Yoshihiro Kawamata
#
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
# 
#   * Neither the name of Yoshihiro Kawamata nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


#==================================
# global variables
#==================================
# 
# global parameters
#    bootmode   : initial boot mode - e.g. 'usbflash'
#    devname    : path of target device for sync - e.g. '/dev/sd0d'
#    uconf      : name of configuration - e.g. 'userdemo'
# 
# constants
#    fuguita_sys_mb : system size of FuguIta in MB
#    fuguita_uefi_kb: Partition size for EFI Sys
#    osrel      : OpenBSD's release - e.g. '6.4'
#    hwmac      : name of the platform - e.g. 'amd64'
#    verarch    : $osrel/$hwmac
# 
# target disks
#    newdev     : device name to newdrive - e.g. 'sd0'
#    parttype   : partition type - MBR/GPT/Hybrid
#    instsys    : boot type - none/mbr (should be none/Legacy/UEFI)
# 
# results of disk_scan()
#    scandev    : name of device, e.g. 'sd0'
#    sect_size  : sector size - bytes/sector
#    sects_trk  : number of sectors per track
#    sects_total: number of total sectors
#    bound_start: start of OpenBSD boundary
#    bound_end  : end of OpenBSD boundary
#    diskscan   : result of scan - 'ok' or 'ng'
# 
# partition sectors (ps_ for short)
#    ps_free    : available sectors for user's partitioning
#    ps_hgap    : head gap (for MBR/GPT and disklabel)
#    ps_uefi    : sectors for UEFI (partition i)
#    ps_fisys   : sectors for FuguIta's system (partition a)
#    ps_fiswp   : sectors for FuguIta's Swap (partition b)
#    ps_fidat   : sectors for FuguIta's Data Store (partition d)
#    ps_fat     : sectors for Data Exchange (partition i or j)
#    ps_tgap    : tail gap (for GPT)
#
# crypto stuffs
#    enc_fidat  : Is FuguIta's Data Store encrypted?
#    enc_pp1    : Primary entered passphrase
#    enc_ppfile : file containing passphrase
# 
# memory-based file systems
#
#    memfstype  : FS type of /ram on currently running system
#    memfs_new  : FS type of /ram for newdrive
# 
# lock directory and mount points
#
#    lockdir    : lock directory
#    mntdir1    : mount point for newdrive
#    mntdir2    : mount point to change /ram FS type


#==================================
# Utility functions and subcommands
#==================================

#-------------------
# echo to stderr
#
echoerr () {
    echo "$@" >&2
}

#-------------------
# clean-ups then exit
#
clear_exit () {
    local exitstat="$1"
    local mnt="$(mount)"
    local dir

    # remove mount points
    #
    for dir in "$mntdir1" "$mntdir2"; do
        if [[ -d "$dir" ]]; then
            if echo "$mnt" | grep -q " on $dir type " ;then
                umount -f "$dir"
            fi
            rmdir "$dir"
        fi
    done

    # remove stale files
    #
    rm -f "$enc_ppfile" "$lockdir/rl_words" "$lockdir/hybrid.mbr"
    
    # remove lock directory
    #
    if ! rmdir "$lockdir"; then
        echoerr "${0##*/}: cannot remove $lockdir, please check."
    fi
    exit "$exitstat"
}

#-------------------
# Wait for ENTER key pressed
# outputs results to stdout
#
#     usage: wait_enter prompt
#
#       output ... 1: ENTER key pressed
#                 -1: Key pressed but not ENTER
#                 -2: Error occured
#
wait_enter () {
    local prompt="$1"
    local line

    echoerr -n "$prompt -> "; read line

    if [[ -z "$line" ]]; then
        echo 1
    else
        echo -1
    fi
}

#-------------------
# ask user yes or no
# outputs answer to stdout
#
#     usage: ask_yn prompt yn
#
#       yn ... y: defaults to yes
#              n: defaults to no
#              r: no default ... ask again
#              else: no default ... return -1 if answered not yn
#
#       output ... 1: yes, 0: no, -1: else yn, -2: error occured
#
ask_yn () {

    if [ -z "$2" ]; then
        echo -2
        return
    fi

    local prompt="$1"; shift
    local yn_default="$1"; shift
    local yn_ans

    case X"$yn_default"X in
        X[Yy]X) yn_default=Y; prompt="$prompt [Y/n] -> " ;;
        X[Nn]X) yn_default=N; prompt="$prompt [y/N] -> " ;;
        X[Rr]X) yn_default=R; prompt="$prompt [y/n] -> " ;;
        *)      yn_default=E; prompt="$prompt [y/n] -> " ;;
    esac

    while :; do
        echoerr -n "$prompt"; read yn_ans

        case X"$yn_ans"X in
            X[Yy]X) echo 1; return;;
            X[Nn]X) echo 0; return;;
            XX)
                case X"$yn_default"X in
                    XYX) echo 1;  return;;
                    XNX) echo 0;  return;;
                    XRX) continue;;
                    *)   echo -1; return;;
                esac;;
            *)
                continue;;
        esac
    done
}

#-------------------
# ask selection out of multiple items
# outputs answer to stdout
#
#     usage: ask_which prompt default item1 item2 ...
#
#       Note: although user's choice is one originated
#             default is zero originated
#
#       output: first word of selected item
#               returns empty line unless selected normally
#
ask_which () {
    local oifs="$IFS"

    if [ $# -lt 3 ]; then
        return
    fi

    local prompt="$1";  shift
    local default="$1"; shift

    # skip null item
    #
    local item val
    local i=0
    for val in "$@"; do
        if [ -n "$val" ]; then
           item[$i]="$val"
           i=$((i+1))
        fi
    done

    # only one item is default itself
    #
    [ "${#item[@]}" = 1 ] && default=${item[0]}

    i=0
    while [ -n "${item[$i]}" ]; do
        if [ "$default" = "${item[$i]}" ]; then
            IFS= prompt="$prompt\n"$(printf '%3d: [%s]' $((i+1)) ${item[$i]})
            IFS="$oifs"
        else
            IFS= prompt="$prompt\n"$(printf '%3d:  %s' $((i+1)) ${item[$i]})
            IFS="$oifs"
        fi
        i=$((i+1))
    done
    echo "$prompt" >&2

    local ans
    ans=$(rl_wread '' '')

    # take first argument
    #
    set -- $ans
    ans=$1
    
    # return selected item
    #
    if expr "$ans" : '^[0-9][0-9]*$' >/dev/null && \
       [ "$ans" -le ${#item[@]} ]; then
        set -- ${item[$((ans-1))]}
        echo $1
    elif [ -n "$default" -a -z "$ans" ]; then
        set -- $default
        echo $1
    fi
}

#-------------------
# read user's input with readline functionality
# outputs echoed to stdout
#
#     usage: rl_wread prompt-str default-str [completion words ....]
#
rl_wread () {
    local prompt="$1";  shift
    local default="$1"; shift
    local retval

    # check if rlwrap is available
    #   When control tty is missing (in /etc/rc.shutdown for example),
    #   rlwrap in command substitution "$(rlwrap ...) " fails.
    if retval=$(rlwrap true) 2>/dev/null 2>&1 ; then
        echo "$@" > $lockdir/rl_words
        rlwrap -b '' \
               -f $lockdir/rl_words \
               -P "$default" \
               sh -f -c 'echo -n "'"$prompt"'->" >&2 ; read w || echo EOF; echo $w' || echo RL_ERR
    else
        #-------------------
        # fallback to dumb input
        #
        if [[ -z "$default" ]]; then
            echo -n "${prompt}->" >&2
            read w
        else
            echo -n "$prompt [$default] -> " >&2
            read w
            if [[ -z "$w" ]]; then
              w="$default"
            fi
        fi
        echo $w
    fi
}

notice () {
    echo
    echo ========================================
    echo = "$@"
    echo =
}

closenotice () {
    echo =
    echo = "$@"
    echo ========================================
    echo
}

#-------------------
# convert bytes to sectors
# bytes can be suffixed by 'k', 'm', 'g' or 't'
#
# Outputs to stdout
#   null string unless converted sucessfully
#
byte2sect () {
    local args=$@  # concat all arguments as a string
    local val
    val=$(expr "$args" : '0*\([1-9][0-9]*\) *[Tt][Bb]*$') && { echo "$((1024*1024*1024*1024*val/sect_size))"; return; }
    val=$(expr "$args" : '0*\([1-9][0-9]*\) *[Gg][Bb]*$') && { echo "$((1024*1024*1024*val/sect_size))";      return; }
    val=$(expr "$args" : '0*\([1-9][0-9]*\) *[Mm][Bb]*$') && { echo "$((1024*1024*val/sect_size))";           return; }
    val=$(expr "$args" : '0*\([1-9][0-9]*\) *[Kk][Bb]*$') && { echo "$((1024*val/sect_size))";                return; }
    val=$(expr "$args" : '0*\([1-9][0-9]*\) *[Bb]*$')     && { echo "$((val/sect_size+1))";                   return; }
    val=$(expr "$args" : '00*') && { echo 0; return; }
}

#-------------------
# convert sectors to bytes
#
# Outputs to stdout
#   null string unless converted sucessfully
#
sect2byte () {
    if expr "$1" : '00*$' >/dev/null; then
        echo "0B"
        return
    fi

    local scale
    case "$2" in
        2) scale=$((1024*1024));;
        3) scale=$((1024*1024*1024));;
        *) scale=1024;;
    esac

    local val
    if val=$(expr "$1" : '\([1-9][0-9]*\)'); then
        val=$((sect_size*val))
        [ "$val" -lt $scale ] && { echo "${val}B"; return; }
        val=$((val/1024)); [ "$val" -lt $scale ] && { echo "${val}KB"; return; }
        val=$((val/1024)); [ "$val" -lt $scale ] && { echo "${val}MB"; return; }
        val=$((val/1024)); [ "$val" -lt $scale ] && { echo "${val}GB"; return; }
        val=$((val/1024)); echo "${val}TB"; return
    fi
}

#-------------------
# convert device name to DUID
#
# Usage: dev2duid device_name
#
#     device_name: e.g. 'sd1'
#
#   outputs DUID to stdout
#   or no output if conversion failed
#
dev2duid () {

    # check device name
    #
    if ! expr "$1" : '[ws]d[0-9]' >/dev/null && \
       ! expr "$1" : 'vnd[0-9]' >/dev/null; then
        return
    fi
    local _dev="$1"

    # scan list of devices
    #
    local _dev_duid
    for _dev_duid in $(sysctl -n hw.disknames | tr , ' '); do
        if expr $_dev_duid : $_dev >/dev/null; then
            echo ${_dev_duid##*:}
            return
        fi
    done
}

#-------------------
# copy directory tree with progress display
#
# Usage: copydirs srcdir destdir
#
copydirs () {
    local srcdir;  srcdir="$1"
    local destdir; destdir="$2"
    [ -d "$srcdir" ]  || { echoerr "source directory $srcdir not found";       return; }
    [ -d "$destdir" ] || { echoerr "destination directory $destdir not found"; return; }
    if which pv >/dev/null 2>&1 ; then
        local srcsize=$(df -k /ram | awk '/ \/ram$/ { print $3 "K"}')
        echo "copying $srcdir to $destdir (${srcsize}B approx.):"
        (cd "$srcdir" && pax -wX -x cpio .) | (pv -s $srcsize; echo -n "waiting for pax to finish ... " >&2) | (cd "$destdir" && pax -rpe)
        (echo -n "syncing ... "; sync; sync; sync; echo done.) >&2
    else
        (while :; do sleep 15; pkill -INFO dd || exit; done) &
        pax -wX -x cpio "$srcdir" | dd bs=1m | (cd "$destdir" && pax -rpe)
    fi
}

#-------------------
# scan specified disk
# then outputs its parameters with form of sh var assignment
#
# Usage: eval $(disk_scan disk)
#
# Outputs: scandev    : name of device, e.g. 'sd0'
#          sect_size  : sector size - bytes/sector
#          sects_trk  : number of sectors per track
#          sects_total: number of total sectors
#          diskscan   : result of scan - 'ok' or 'ng'
#
disk_scan () {
    [ -z "$1" ] && return
    disklabel -c "$1" | awk '
BEGIN {
    paramfound = 0
    print "scandev='"$1"'"
}
$1 == "bytes/sector:"  { sect_size=$2;   print "sect_size=" sect_size; paramfound++; }
$1 == "sectors/track:" { print "sects_trk=" $2; paramfound++ }
$1 == "boundstart:"    { print "bound_start=" $2; paramfound++ }
$1 == "boundend:"      { print "bound_end=" $2; paramfound++ }
/^total sectors: /     { sects_total=$3; print "sects_total=" $3; paramfound++; }
END {
    if (paramfound==5) {
        print "diskscan=ok"
    } else
        print "diskscan=ng"
}'
}

#-------------------
# show global parameters
#
# Usage: showparams ['verbose']
#
#   verbose is for debug output
#
showparams () {

    echo
    echo "target disk: $scandev"
    echo "  partition type=$parttype"
    echo "       boot type=$instsys"
    echo
    if [ "$1" = verbose ]; then
        cat<<EOT | awk 'BEGIN{FS=","} {printf("%20s : %10s\n", $1, $2)}'
--------------------,----------
partition,size
--------------------,----------
whole disk,$sects_total
partition tables,${ps_hgap}+${ps_tgap}
UEFI system,$ps_uefi
FuguIta system,$ps_fisys
FuguIta swap,$ps_fiswp
FuguIta user data,$ps_fidat
MSDOS FAT,$ps_fat
--------------------,----------
EOT
        echo
        echo '===== fdisk =========='
        fdisk -v $scandev | egrep '[0-9]: |^(Primary GPT|Secondary GPT|MBR):'
        echo
        echo '===== disklabel ======'
        disklabel -c $scandev | egrep '^  [a-p]:|^bound'
    else
        cat<<EOT | awk 'BEGIN{FS=","} {printf("%20s : %6s\n", $1, $2)}'
--------------------,------
partition,size
--------------------,------
whole disk,$(sect2byte $sects_total)
partition tables,$(sect2byte $((ps_hgap+ps_tgap)))
UEFI system,$(sect2byte $ps_uefi)
FuguIta system,$(sect2byte $ps_fisys)
FuguIta swap,$(sect2byte $ps_fiswp)
FuguIta user data,$(sect2byte $ps_fidat)
MSDOS FAT,$(sect2byte $ps_fat)
--------------------,------
EOT
    fi
}

#-------------------
# zero-filling specified disk 1MB from the head
#
#     Usage: zerofill_head
#
# results of previous execution of disk_scan affects this.
#
zerofill_head () {
    notice "Clearing MBR, GPT and BSD disklabel"
    [ "$diskscan" = ok ]  || return

    # clear partition table and disklabel
    dd if=/dev/zero of=/dev/r"$scandev"c bs=$((2048*sect_size)) count=$(((bound_start+$(byte2sect 1M))/2048))
}

#-------------------
# setup MBR type fdisk partitions
#
#     Usage: fdisk_init partition-type
#
# results of previous execution of disk_scan affects this.
#
fdisk_init () {
    [ "$diskscan" = ok ]  || return
    [ -z "$1" ] && return

    local ptype="$1"

    if [ ! "$sects_total" = $((ps_hgap+ps_uefi+ps_fisys+ps_fiswp+ps_fidat+ps_fat+ps_tgap)) ]; then
        echoerr "Inconsistent sector sizes: $sects_total != $ps_hgap + $ps_uefi + $ps_fisys + $ps_fiswp + $ps_fidat + $ps_fat + $ps_tgap"
        return
    fi

    notice "Setting up fdisk partitions"

    if [ "$debugfile" ]; then
        { echo
          echo "////////// fdisk_init ////////////////////////////////////////"
          showparams verbose
        } >> "$debugfile"
    fi

    # - initialize partition table
    # - build input string for fdisk
    #
    local fdisk_input=''
    case "$ptype" in
        MBR)
            fdisk -iy $scandev
            [ 0 -lt $ps_uefi ] && fdisk_input="e 0\nEF\nn\n${ps_hgap}\n${ps_uefi}\n"
            [ 0 -lt $ps_fat ]  && fdisk_input="${fdisk_input}e 2\n0C\nn\n$((ps_hgap+ps_uefi+ps_fisys+ps_fiswp+ps_fidat))\n${ps_fat}\n"
            [ 0 -lt $((ps_fisys+ps_fiswp+ps_fidat)) ] && fdisk_input="${fdisk_input}e 3\nA6\nn\n$((ps_hgap+ps_uefi))\n$((ps_fisys+ps_fiswp+ps_fidat))\n"
            fdisk_input="${fdisk_input}quit\n"
            ;;
        GPT)
            fdisk -igy $scandev
            fdisk_input="e 0\n0\n"
            [ 0 -lt $ps_uefi ] && fdisk_input="${fdisk_input}e 1\nEF\n${ps_hgap}\n${ps_uefi}\nUEFI Boot\n"
            [ 0 -lt $ps_fat ]  && fdisk_input="${fdisk_input}e 2\n0C\n$((ps_hgap+ps_uefi+ps_fisys+ps_fiswp+ps_fidat))\n$ps_fat\nMSDOS FAT\n"
            [ 0 -lt $((ps_fisys+ps_fiswp+ps_fidat)) ] && fdisk_input="${fdisk_input}e 3\nA6\n$((ps_hgap+ps_uefi))\n$((ps_fisys+ps_fiswp+ps_fidat))\nOpenBSD Area\n"
            fdisk_input="${fdisk_input}quit\n"
            ;;
        Hybrid)
            fdisk -iy $scandev
            fdisk_input="e 0\nEE\nn\n1\n*\n"
            [ 0 -lt $ps_uefi ] && fdisk_input="${fdisk_input}e 1\nEF\nn\n${ps_hgap}\n${ps_uefi}\n"
            [ 0 -lt $ps_fat ]  && fdisk_input="${fdisk_input}e 2\n0C\nn\n$((ps_hgap+ps_uefi+ps_fisys+ps_fiswp+ps_fidat))\n$ps_fat\n"
            [ 0 -lt $((ps_fisys+ps_fiswp+ps_fidat)) ] && fdisk_input="${fdisk_input}e 3\nA6\nn\n$((ps_hgap+ps_uefi))\n$((ps_fisys+ps_fiswp+ps_fidat))\n"
            fdisk_input="${fdisk_input}quit\n"
            ;;
    esac

    # invoke fdisk
    #
    if [ -n "$fdisk_input" ]; then
        if [ "$debugfile" ]; then
            { echo
              echo "===== fdisk input ====="
              echo "$fdisk_input"
            } >> "$debugfile"
        fi
        echo -n "$fdisk_input" | fdisk -e "$scandev" >/dev/null
    fi
}

#-------------------
# setup OpenBSD's partition and all filesystems
#
#     usage: setup_fs
#
# With "Legacy", partition a is prepared and
# FuguIta system will be copied.
#
setup_fs () {
    [ "$diskscan" = ok ]  || return

    if [ 0 -eq $(expr "$scandev" : '^[sw]d[0-9]$') ] &&
       [ 0 -eq $(expr "$scandev" : '^vnd[0-9]$') ]; then
        return
    fi

    notice "Setting up disklabel and FFS partitions"

    if [ "$debugfile" ]; then
        { echo
          echo "////////// setup_fs ////////////////////////////////////////"
          showparams verbose
        } >> "$debugfile"
    fi

    # build input string for disklabel
    #
    local disklabel_input='' part

    # commands to delete existing FFS/RAID/swap partitions
    for part in $(disklabel -c $scandev | awk '/^  [a-p]: .*  (4\.2BSD|RAID|swap)/{print $1}' | tr -d :); do
        disklabel_input="${disklabel_input}d $part\n"
    done

    # commands to create new FFS partitions
    [ 0 -lt $ps_fisys ] && disklabel_input="${disklabel_input}a a\n$((ps_hgap+ps_uefi))\n${ps_fisys}\n4.2BSD\n"
    [ 0 -lt $ps_fiswp ] && disklabel_input="${disklabel_input}a b\n$((ps_hgap+ps_uefi+ps_fisys))\n${ps_fiswp}\nSWAP\n"
    if [ 0 -lt $ps_fidat ]; then
        local fstype='4.2BSD'
        [ "$enc_fidat" = 'yes' ] && fstype='RAID'
        disklabel_input="${disklabel_input}a d\n$((ps_hgap+ps_uefi+ps_fisys+ps_fiswp))\n*\n${fstype}\n"
    fi
    disklabel_input="${disklabel_input}q\ny\n"

    # invoke disklabel
    #
    if [ -n "$disklabel_input" ]; then
        if [ "$debugfile" ]; then
            { echo
              echo "===== disklabel input ====="
              echo "$disklabel_input"
            } >> "$debugfile"
        fi
        echo -n "$disklabel_input" | disklabel -c -E "$scandev" >/dev/null
    fi

    local disklabel=$(disklabel -c $scandev)

    echo "$disklabel" | grep -q 'i:.*MSDOS' && newfs_msdos ${scandev}i
    echo "$disklabel" | grep -q 'j:.*MSDOS' && newfs_msdos ${scandev}j

    if echo "$disklabel" | grep -q 'a:.*4\.2BSD'; then
        # very few files ... make inode density low
        newfs -O 1 -m 0 -o space -i$((fuguita_sys_mb*1024*1024/100)) ${scandev}a

        notice "Copying FuguIta system files"

        mount -o async /dev/${scandev}a $mntdir1
        (cd /sysmedia
         pax -rwvpe boot bsd-fi bsd-fi.mp cdboot cdbr etc $mntdir1
         [[ -e eficdboot ]] && cp -pv eficdboot $mntdir1)

        notice "Transferring filesystem image"
        if [ -r /sysmedia/fuguita-${osrel}-${hwmac}.ffsimg ]; then
            if which pv >/dev/null 2>&1 ; then
                pv /sysmedia/fuguita-${osrel}-${hwmac}.ffsimg | dd of=$mntdir1/fuguita-${osrel}-${hwmac}.ffsimg bs=1m
            else
                (while :; do sleep 15; pkill -INFO dd || exit; done) &  # to display progress
                dd if=/sysmedia/fuguita-${osrel}-${hwmac}.ffsimg of=$mntdir1/fuguita-${osrel}-${hwmac}.ffsimg bs=1m
            fi
        fi

        # To change FS type mounted on /ram,
        # rewrite setting in file system image
        #
        if [[ $instsys != none ]]; then
            local vn=$(vnconfig $mntdir1/fuguita-${osrel}-${hwmac}.ffsimg)
            if [[ -z "$vn" ]]; then
                echoerr 'no available vnode device, FS type of /ram not chagned'
            else
                mkdir -p $mntdir2
                mount /dev/${vn}a $mntdir2
                if grep -q "memfstype=$memfs_new" $mntdir2/etc/fuguita/global.conf; then
                    :  # same value already - do nothing
                else
                    # rewrite the value
                    sed -i -e '1,$s/memfstype=[a-z][a-z]*fs/memfstype='$memfs_new'/' $mntdir2/etc/fuguita/global.conf
                fi
                umount $mntdir2
                rmdir $mntdir2
                vnconfig -u $vn
            fi
        fi

        case $instsys in
            Legacy|Hybrid)
                notice "Installing BIOS boot loader"
                /usr/sbin/installboot -v -r $mntdir1 ${scandev} /usr/mdec/biosboot /usr/mdec/boot
                umount $mntdir1
                ;;
            UEFI)
                # if MBR exists, install MBR loader also
                #
                if [ "$parttype" = MBR \
                                 -a -f /fuguita/usr/mdec/biosboot \
                                 -a -f /fuguita/usr/mdec/boot ]; then
                    notice "Installing BIOS loader"
                    /usr/sbin/installboot -v -r $mntdir1 ${scandev} /fuguita/usr/mdec/biosboot /fuguita/usr/mdec/boot
                fi
                umount $mntdir1

                notice "Installing UEFI boot loader"
                if mount -t msdos -o -l /dev/${scandev}i $mntdir1; then
                    mkdir -p $mntdir1/efi/BOOT
                    cp /fuguita/usr/mdec/*.EFI $mntdir1/efi/BOOT
                    umount $mntdir1
                fi
                ;;
        esac

        notice "Checking the partition for system files"
        fsck -fy /dev/r${scandev}a
    fi

    local datdev=$scandev  # datdev will be decrypted device if encryption specified

    # setup crypto partition
    #
    if echo "$disklabel" | grep -q 'd:.*RAID'; then
        notice 'Setting up for data store encryption'

        # setup passphrase file
        #
        rm -f $enc_ppfile
        local def_umask=$(umask)
        umask 077
        echo "$enc_pp1" > $enc_ppfile
        chmod 0600 $enc_ppfile
        umask $def_umask
        unset enc_pp1

        # create a crypto volume
        #
        bioctl -p $enc_ppfile -c C -l /dev/${scandev}d softraid0
        rm -f $enc_ppfile

        # check and get created device
        #
        datdev=$(bioctl -i softraid0 \
                | awk "/^softraid.*CRYPTO\$/ { dev=\$(NF-1) }
                       /<${scandev}d>\$/     { datdev=dev }
                       END                   { print datdev }")

        # create a FFS partition in the crypto volume
        #
        if [ 0 -lt $(expr "$datdev" : '^sd[0-9]') ]; then
            fdisk -iy $datdev
            echo "a d\n\n*\n4.2BSD\nw\nq\n" | disklabel -c -E $datdev >/dev/null
        else
            return  # crypto volume not found
        fi
    fi

    # setup data store device
    #   datdev is decrypted device if encryption specified
    #   otherwise same as scandev
    #
    if disklabel -c $datdev | grep -q 'd:.*4\.2BSD'; then
        notice "Setting up a partition for user's data"
        if [ $ps_fidat -lt $(byte2sect 2G) ]; then
            # adjust inode density for little space
            # because too many symlinks when booted mode 0
            newfs -O 1 -m 0 -o space -i1024 -b4096 -f512 ${datdev}d
        elif [ $ps_fidat -lt $(byte2sect 4G) ]; then
            newfs -O 2 -m 0 -o space ${datdev}d
        elif [ $ps_fidat -lt $(byte2sect 8G) ]; then
            newfs -O 2 -m 0 ${datdev}d
        else
            newfs -O 2 -m 0 -o time ${datdev}d
        fi

        local sys_duid=$(dev2duid $scandev)
        local dat_duid=$(dev2duid $datdev)
        mount -o async /dev/${datdev}d $mntdir1
        mkdir -p $mntdir1/livecd-config/${verarch}
        local umem="75%"
        [[ "$memfs_new" = 'tmpfs' ]] && umem=0
        cat <<EOT >$mntdir1/livecd-config/${verarch}/noasks
#
# noasks - parameter settings for non-interactive boot
#
# Make statements uncommented
# Then assign real values
#
#
# FuguIta system device
#${sys_duid:+   - Use one of two lines}
#noask_rdev=${scandev}a  # device name format
#${sys_duid:+noask_rdev=${sys_duid}.a  # DUID format}
#
# max ${memfs_new} size
#noask_umem=${umem}
#
# boot mode
#noask_setup_rw_mode=3
#
# storage device
#${dat_duid:+   - Use one of two lines}
#noask_confdev=${datdev}d  # device name format
#${dat_duid:+noask_confdev=${dat_duid}.d  # DUID format}
#
# data set name in USB flash drive
#noask_confdata=$(hostname -s)
EOT
        umount $mntdir1

        notice "Checking the partition for persistent storage"
        fsck -fy /dev/r${datdev}d
    fi

    # delete crypto volume
    #
    if echo "$disklabel" | grep -q 'd:.*RAID'; then
        # detach the device
        bioctl -d ${datdev}
    fi
}

#-------------------
# clean-ups for vnode device
#
#     usage: clear_vn vn_device vn_img_rm
#
#    vn_device : vnode to be unattached 
#    vn_img_rm : remove this file if specified
#
clear_vn () {
    local vndev="$1"
    local vnrm="$2"

    case "$vndev" in
        vnd[0-3])
            if vnconfig -l | grep -q "^$vndev: covering "; then
                vnconfig -u "$vndev"
            fi
            ;;
    esac
    [ -n "$vnrm" ] && rm -f $vnrm
}

#-------------------
# Convert given partition name to partition GUID
#
#     usage: getguid partition-name
#
#    partition-name: GPT partition name displayed by fdisk
#    parition GUID is output to stdout
#
#    if not found, returns none "empty string"
#
getguid () {
    local guidtab="193d1ea4-b3ca-11e4-b075-10604b889dcf,Android 6.0+ ARM EXT
19a710a2-b3ca-11e4-b026-10604b889dcf,Android 6.0+ ARM Meta
49a4d17f-93a3-45c1-a0de-f50b2ebe2599,Android-IA Boot
2568845d-2332-4675-bc39-8fa5a4748d15,Android-IA Bootloader
114eaffe-1552-4022-b26e-9b053604cf84,Android-IA Bootloader2
a893ef21-e428-470a-9e55-0668fd91a2d9,Android-IA Cache
bd59408b-4514-490d-bf12-9878d963f378,Android-IA Config
dc76dda9-5ac1-491c-af42-a82591580c0d,Android-IA Data
8f68cc74-c5e5-48da-be91-a0c8c15e9c80,Android-IA Factory
9fdaa6ef-4b3f-40d2-ba8d-bff16bfb887b,Android-IA Factory (alt)
767941d0-2085-11e3-ad3b-6cfdb94711e9,Android-IA Fastboot/Tertiary
20ac26be-20b7-11e3-84c5-6cfdb94711e9,Android-IA Metadata
ef32a33b-a409-486c-9141-9ffb711f6266,Android-IA Misc
ac6d7924-eb71-4df8-b48d-e267b27148ff,Android-IA OEM
ebc597d0-2053-4b15-8b64-e0aac75f4db1,Android-IA Persistent
4177c722-9e92-4aab-8644-43502bfd5506,Android-IA Recovery
38f428e6-d326-425d-9140-6e0ea133647c,Android-IA System
c5a0aeec-13ea-11e5-a1b1-001e67ca0c3c,Android-IA Vendor
90b6ff38-b98f-4358-a21f-48f35b4a8ad3,ArcaOS Type 1 (OS/2)
734e5afe-f61a-11e6-bc64-92361f002671,Atari TOS Basic data
4778ed65-bf42-45fa-9c5b-287a1dc4aab1,Barebox barebox-state
42465331-3ba3-10f1-802a-4861696b7521,BeOS/i386
cafecafe-9b03-4f30-b4c6-b4b80ceff106,Ceph Block
30cd0809-c2b2-499c-8879-2d6b78529876,Ceph Block DB
5ce17fce-4087-4169-b7ff-056cc58473f9,Ceph Block write-ahead log
89c57f98-2fe5-4dc0-89c1-f3ad0ceff2be,Ceph Disk in creation
45b0969e-9b03-4f30-b4c6-b4b80ceff106,Ceph Journal
fb3aabf9-d25f-47cc-bf5e-721d1816496b,Ceph Lockbox for dm-crypt keys
4fbd7e29-8ae0-4982-bf9d-5a8d867af560,Ceph Multipath OSD
cafecafe-8ae0-4982-bf9d-5a8d867af560,Ceph Multipath block A
7f4a666a-16f3-47a2-8445-152ef4d03f6c,Ceph Multipath block B
ec6d6385-e346-45dc-be91-da2a7c8b3261,Ceph Multipath block DB
01b41e1b-002a-453c-9f17-88793989ff8f,Ceph Multipath block log
45b0969e-8ae0-4982-bf9d-5a8d867af560,Ceph Multipath journal
4fbd7e29-9d25-41b8-afd0-062c0ceff05d,Ceph OSD
4fbd7e29-9d25-41b8-afd0-35865ceff05d,Ceph dm-crypt LUKS OSD
cafecafe-9b03-4f30-b4c6-35865ceff106,Ceph dm-crypt LUKS block
166418da-c469-4022-adf4-b30afd37f176,Ceph dm-crypt LUKS block DB
45b0969e-9b03-4f30-b4c6-35865ceff106,Ceph dm-crypt LUKS journal
86a32090-3647-40b9-bbbd-38d8c573aa86,Ceph dm-crypt LUKS log
4fbd7e29-9d25-41b8-afd0-5ec00ceff05d,Ceph dm-crypt OSD
cafecafe-9b03-4f30-b4c6-5ec00ceff106,Ceph dm-crypt block
93b0052d-02d9-4d8a-a43b-33a3ee4dfbc3,Ceph dm-crypt block DB
306e8683-4fe2-4330-b7c0-00a917c16966,Ceph dm-crypt block log
89c57f98-2fe5-4dc0-89c1-5ec00ceff2be,Ceph dm-crypt disk in creation
45b0969e-9b03-4f30-b4c6-5ec00ceff106,Ceph dm-crypt journal
cab6e88e-abf3-4102-a07a-d4bb9be3c1d3,ChromeOS firmware
2e0a753d-9e48-43b0-8337-b15192cb1b5e,ChromeOS future use
3f0f8318-f146-4e6b-8222-c28c8f02e0d5,ChromeOS hibernate
09845860-705f-4bb5-b16c-8a8a099caf52,ChromeOS miniOS
3cb8e202-3b7e-47dd-8a3c-7ff2a13cfcec,ChromeOS rootfs
5dfbf5f4-2848-4bac-aa5e-0d9a20b745a6,CoreOS /usr
c95dc21a-df0e-4340-8d7b-26cbfa9a03e0,CoreOS OEM
3884dd41-8582-4404-b9a8-e9b84f2df50e,CoreOS Resizable root
be9067b9-ea49-4f15-b4f6-f36f8c9e1818,CoreOS root RAID
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,DOS > 32MB
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,DOS FAT-12
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,DOS FAT-16
c12a7328-f81f-11d2-ba4b-00a0c93ec93b,EFI Sys
516e7cb4-6ecf-11d6-8ff8-00022d09712b,FreeBSD
83bd6b9d-7f41-11dc-be0b-001560b84f0f,FreeBSD Boot
516e7cb5-6ecf-11d6-8ff8-00022d09712b,FreeBSD Swap
516e7cb6-6ecf-11d6-8ff8-00022d09712b,FreeBSD UFS
516e7cb8-6ecf-11d6-8ff8-00022d09712b,FreeBSD Vinum volume manager
516e7cba-6ecf-11d6-8ff8-00022d09712b,FreeBSD ZFS
74ba7dd9-a689-11e1-bd04-00e081286acf,FreeBSD nandfs
a13b4d9a-ec5f-11e8-97d8-6c3be52705bf,Fuchsia Legacy Verified boot mA
a288abf2-ec5f-11e8-97d8-6c3be52705bf,Fuchsia Legacy Verified boot mB
6a2460c3-cd11-4e8b-80a8-12cce268ed0a,Fuchsia Legacy Verified boot mR
de30cc86-1f4a-4a31-93c4-66f147d33e05,Fuchsia Legacy Zircon boot (A)
23cc04df-c278-4ce7-8471-897d1a4bcdf7,Fuchsia Legacy Zircon boot (B)
a0e5cf57-2def-46be-a80c-a2067c37cd49,Fuchsia Legacy Zircon boot (R)
2967380e-134c-4cbb-b6da-17e7ce1ca45d,Fuchsia Legacy blob
5ece94fe-4c86-11e8-a15b-480fcf35f8e6,Fuchsia Legacy bootloader
08185f0c-892d-428a-a789-dbeec8f55e6a,Fuchsia Legacy data
900b0fc5-90cd-4d4f-84f9-9f8ed579db88,Fuchsia Legacy emmc-boot1
b2b2e8d1-7c10-4ebc-a2d0-4614568260ad,Fuchsia Legacy emmc-boot2
5a3a90be-4c86-11e8-a15b-480fcf35f8e6,Fuchsia Legacy factory-config
41d0e340-57e3-954e-8c1e-17ecac44cff5,Fuchsia Legacy fvm
8b94d043-30be-4871-9dfa-d69556e8c1f3,Fuchsia Legacy guid-test
48435546-4953-2041-494e-5354414c4c52,Fuchsia Legacy install
1d75395d-f2c6-476b-a8b7-45cc1c97b476,Fuchsia Legacy misc
4e5e989e-4c86-11e8-a15b-480fcf35f8e6,Fuchsia Legacy sys-config
606b000b-b7c7-4653-a7d5-b737332c899d,Fuchsia Legacy system
10b8dbaa-d2bf-42a9-98c6-a7c5db3701e7,Fuchsia RO Factory boot data
f95d940e-caba-4578-9b93-bb6c90f29d3e,Fuchsia RO Factory system data
421a8bfc-85d9-4d85-acda-b64eec0133e9,Fuchsia Verified boot meta (ABR)
49fd7cb8-df15-4e73-b9d9-992070127f0f,Fuchsia Volume Manager
9b37fff6-2e58-466a-983a-f7926d0b04e0,Fuchsia Zircon boot (ABR)
fe8a2634-5e2e-46ba-99e3-3a192091a350,Fuchsia bootloader (ABR)
d9fd4535-106c-4cec-8d37-dfc020ca87cb,Fuchsia encrypted system data
a409e16b-78aa-4acc-995c-302352621a41,Fucshia boot metadata (ABR)
75894c1e-3aeb-11d3-b7c1-7b03a0000000,HP-UX Data
e2a1e728-32e3-11d6-a682-7b03a0000000,HP-UX Service
d3bfe2de-3daf-11df-ba40-e3a556d89593,Intel Fast Flash (iFFS)
bfbfafe7-a34f-448a-9a5b-6213eb736c22,Lenovo boot
bc13c2ff-59e6-4262-a352-b275fd6f7172,Linux /boot
933ac7e1-2eb4-4f13-b844-0e14e2aef915,Linux /home
3b8f8425-20e0-4f3b-907f-1a25a76f98e8,Linux /srv (server data)
4d21b016-b534-45c2-a9fb-5c16e091fd2d,Linux /var
7ec6f557-3bc5-4aca-b293-16ef5df639d1,Linux /var/tmp
7d0359a3-02b3-4f0a-865c-654403e70625,Linux Arm32 /usr
c215d751-7bcd-4649-be90-6627490a4c05,Linux Arm32 /usr verity
69dad710-2ce4-4e3c-b16c-21a1d49abed3,Linux Arm32 Root
7386cdf2-203c-47a9-a498-f2ecce45a2d6,Linux Arm32 root verity
b0e01050-ee5f-4390-949a-9101b17104e9,Linux Arm64 /usr
6e11a4e7-fbca-4ded-b9e9-e1a512bb664e,Linux Arm64 /usr verity
b921b045-1df0-41c3-af44-4c6f280d3fae,Linux Arm64 root
df3300ce-d69f-4c92-978c-9bfb0f38d820,Linux Arm64 root verity
4301d2a6-4e3b-4b2a-bb94-9e0b2c4225ea,Linux IA-64 /usr
6a491e03-3be7-4545-8e38-83320e0ea880,Linux IA-64 /usr verity
993d8d3d-f80e-4225-855a-9daf8ed7ea97,Linux IA-64 root
86ed10d5-b607-45bb-8957-d350f23d0571,Linux IA-64 root verity
ca7d7ccb-63ed-4c53-861c-1742536059cc,Linux LUKS
e6d6d379-f507-44c2-a23c-238f2a3df928,Linux LVM
7ffec5c9-2d00-49b7-8941-3ea10a5586b7,Linux Plain dm-crypt
a19d880f-05fc-4d3b-a006-743f0f84911e,Linux RAID
8da63339-0007-60c0-c436-083ac8230908,Linux Reserved
0fc63daf-8483-4772-8e79-3d69d8477de4,Linux files*
0657fd6d-a4ab-43c4-84e5-0933c84b4f4f,Linux swap
773f91ef-66d4-49b5-bd83-d683bf40ad16,Linux user's home
75250d76-8cc6-458e-bd66-bd47cc81a812,Linux x86 /usr
8f461b0d-14ee-4e81-9aa9-049b6fb97abd,Linux x86 /usr verity
44479540-f297-41b2-9af7-d131d5f0458a,Linux x86 Root
d13c5d3b-b5d1-422a-b29f-9454fdc89d76,Linux x86 root verity
8484680c-9521-48c6-9c11-b0720656f69e,Linux x86-64 /usr
77ff5f63-e7b6-4633-acf4-1565b864c0e6,Linux x86-64 /usr verity
4f68bce3-e8cd-4db1-96e7-fbcaf984b709,Linux x86-64 Root
2c7357ed-ebd2-46d9-aec1-23d437ec2bf5,Linux x86-64 root verity
af9b60a0-1431-4f62-bc68-3311714a69ad,LinuxSwap DR
53746f72-6167-11aa-aa11-00306543ecac,MacOS Core Storage container
4c616265-6c00-11aa-aa11-00306543ecac,MacOS Label
52414944-0000-11aa-aa11-00306543ecac,MacOS RAID
52414944-5f4f-11aa-aa11-00306543ecac,MacOS RAID (offline)
5265636f-7665-11aa-aa11-00306543ecac,MacOS TV Recovery
55465300-0000-11aa-aa11-00306543ecac,MacOS X
48465300-0000-11aa-aa11-00306543ecac,MacOS X HFS+
426f6f74-0000-11aa-aa11-00306543ecac,MacOS X boot
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,Microsoft basic data
85d5e45e-237c-11e1-b4b3-e89a8f7fc3a7,MidnightBSD Boot
85d5e45a-237c-11e1-b4b3-e89a8f7fc3a7,MidnightBSD Data
85d5e45b-237c-11e1-b4b3-e89a8f7fc3a7,MidnightBSD Swap
0394ef8b-237e-11e1-b4b3-e89a8f7fc3a7,MidnightBSD UFS
85d5e45c-237c-11e1-b4b3-e89a8f7fc3a7,MidnightBSD Vinum vol mgr
85d5e45d-237c-11e1-b4b3-e89a8f7fc3a7,MidnightBSD ZFS
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,NTFS
49f48d5a-b10e-11dc-b99b-0019d1879648,NetBSD
2db519c4-b10f-11dc-b99b-0019d1879648,NetBSD Concatenated
2db519ec-b10f-11dc-b99b-0019d1879648,NetBSD Encrypted
49f48d82-b10e-11dc-b99b-0019d1879648,NetBSD LFS
49f48daa-b10e-11dc-b99b-0019d1879648,NetBSD RAID
49f48d32-b10e-11dc-b99b-0019d1879648,NetBSD Swap
7412f7d5-a156-4b13-81dc-867174929325,ONIE Boot
d4e6e2cd-4469-46f3-b5cb-1bff57afc149,ONIE Config
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,OS/2 hidden
824cc7a0-36a8-11e3-890a-952519ad3f61,OpenBSD
9e1a2d38-c612-4316-aa26-8b49521e5a8b,PPC PReP boot
c91818f9-8025-47af-89d2-f030d7000c2c,Plan9
cef5a9ad-73bc-4601-89f3-cdeeeee321a1,QNX (6) Power-safe file system
7c5222bd-8f5d-4087-9c00-bf9843c7b58c,SPDK block device
bbba6df5-f46f-4a89-8f59-8765b2727503,Softraid Cache
2e313465-19b9-463f-8126-8a7993773801,Softraid Scratch
b6fa30da-92d2-4a9a-96f1-871ec6486200,Softraid Status
fa709c7e-65b1-4593-bfd5-e71d61de9b02,Softraid Volume
6a85cf4d-1dd2-11b2-99a6-080020736631,Solaris
6a90ba39-1dd2-11b2-99a6-080020736631,Solaris/illumos /home
6a898cc3-1dd2-11b2-99a6-080020736631,Solaris/illumos /usr|MacOS ZFS
6a8ef2e9-1dd2-11b2-99a6-080020736631,Solaris/illumos /var
6a9283a5-1dd2-11b2-99a6-080020736631,Solaris/illumos Alt sector
6a8b642b-1dd2-11b2-99a6-080020736631,Solaris/illumos Backup
6a8d2ac7-1dd2-11b2-99a6-080020736631,Solaris/illumos Reserved
6a945a3b-1dd2-11b2-99a6-080020736631,Solaris/illumos Reserved
6a96237f-1dd2-11b2-99a6-080020736631,Solaris/illumos Reserved
6a9630d1-1dd2-11b2-99a6-080020736631,Solaris/illumos Reserved
6a980767-1dd2-11b2-99a6-080020736631,Solaris/illumos Reserved
6a87c46f-1dd2-11b2-99a6-080020736631,Solaris/illumos Swap
6a82cb45-1dd2-11b2-99a6-080020736631,Solaris/illumos boot
f4019732-066e-4e12-8273-346c5641494f,Sony boot
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,ThinkPad Rec
3de21764-95bd-54bd-a5c3-4abe786f38a8,U-Boot environment
00000000-0000-0000-0000-000000000000,Unused
9198effc-31c0-11db-8f78-000c2911d1b8,VMWare Reserved
aa31e02a-400f-11db-9590-000c2911d1b8,VMWare VMFS filesystem
9d275380-40ad-11db-bf97-000c2911d1b8,VMWare vmkcore (coredump)
8c8f8eff-ac95-4770-814a-21994f2dbc8f,Veracrypt Encrypted data
de94bba4-06d1-4d40-a16a-bfd50179d6ac,Win Recovery
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,Win95 FAT-32
ebd0a0a2-b9e5-4433-87c0-68b6b72699c7,Win95 FAT32L
37affc90-ef7d-4e96-91c3-2d7ae055b174,Windows IBM General Parallel FS
5808c8aa-7e8f-42e0-85d2-e1e90434cfb3,Windows LDM metadata
e3c9e316-0b5c-4db8-817d-f92df00215ae,Windows Reserved (MSR)
558d43c5-a1ac-43c0-aac8-d1472b2923d1,Windows Storage Replica
e75caf8f-f680-4cee-afa3-b001e56efc2d,Windows Storage Spaces
"
    readonly guidtab

    [[ -n "$1" ]] || return

    local partname="$1"
    local guid=$(echo "$guidtab" | grep ",${partname}\$" | tail -n 1)
    guid=${guid%,*}

    [[ -n "$guid" ]] || return

    echo $guid
}

#-------------------
# read every GPT line output from fdisk
# then converts it to command sequence
# to generate equivalent patition
#
#    input: stdin
#    output: stdout
#
#  if error detected, outputs "abort" fdisk command
#
listpart () {
    local oifs="$IFS"

    awk \
        '/^[ 	]*[0-9]+: .* \[ .* \]$/{
             partno=$1+0
             parttype=substr($0, 7, 36); sub(" +$", "", parttype)
             secstart=$(NF-2); secstart=secstart+0
             secend=$(NF-1)
             line1=partno","parttype","secstart","secend
         }

         /^      [0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+ / {
             guid=substr($0, 7, 36); sub(" +$", "", guid)
             partname=substr($0, 44); sub(" +$", "", partname)
             print line1","guid","partname
             line1=""
         }' \
      | while read line; do
          IFS=','
          set -- $line
          IFS="$oifs"
          # echo "$1,$(getguid "$2"),$3,$4,$5,$6"
          cat <<EOT
edit $1
$(getguid "$2")
$3
$4
$6
EOT
      done
}

#-------------------
# read whole GPT output from "fdisk -v"
# then outputs them fdisk command sequences
# to generate equivalent patition tables
#
#    input: stdin
#    output: stdout
#
#  if error detected, outputs "abort" fdisk command
#
fixgpt () {
    # output of fdisk -v
    local fdisk_src="$(cat)"

    # part of Primary GPT
    local fdisk_pri=$(echo "$fdisk_src" | sed '/^Primary GPT:/,/^Secondary GPT:/!d')

    # part of Primary MBR
    local fdisk_mbr=$(echo "$fdisk_src" | sed '/^MBR:/,$!d')

    # check if this disk contains GPT
    if ! echo "$fdisk_mbr" | grep -q '^ 0: EE .* EFI GPT$'; then
        echoerr "not GPT disk"
        echo "abort"
        return
    fi

    # get command sequence to generate equivalent GPT
    local gpt_pri=$(echo "$fdisk_pri" | listpart)

    # generate fdisk command sequences?
    if [[ -z "$gpt_pri" ]]; then
        echoerr "Illegal primary GPT"
        echo "abort"
        return
    fi

    # initialize GPT (only an OpenBSD partition generated)
    echo "reinit gpt"

    # disables it
    echo "edit 0"
    echo "0"

    # rebuild equivalent GPT
    echo "$gpt_pri"

    # write to disk
    echo "write"
    echo "quit"
}


#==================================
# Interactive commands
#==================================

#-------------------
# sync - write back memory file system
#        to the storage device
#
cmd_sync () {
    echo
    if [[ -z "$devname" ]]; then
        echoerr "Name of device not set. Use 'target' to perform this."
        return 1
    fi
    if [[ -z "$uconf" ]]; then
        echoerr "Name of saving data not set. Use 'saveas' to perform this."
        return 1
    fi

    notice "sync current ${memfstype} as $uconf"
    if [ 1 = $(ask_yn "OK?" n) ]; then
        if mount -o async,noatime $devname $mntdir1; then
            echo
            if [ -d $mntdir1/livecd-config ]; then
                if [ -d "$mntdir1/livecd-config/$verarch/$uconf" ]; then
                    # previous backup exists ... do differencial copy
                    rsync -v -rlptgo --devices -xHS --delete --include '*/tmp' --exclude 'tmp/*' /ram/. $mntdir1/livecd-config/$verarch/$uconf/.
                else
                    # first backup ... use cpio
                    mkdir -p "$mntdir1/livecd-config/$verarch/$uconf"
                    copydirs /ram "$mntdir1/livecd-config/$verarch/$uconf"
                    rm -rf $mntdir1/livecd-config/$verarch/"$uconf"/tmp/{.??*,*}
                fi

                # not to reset $uconf at subsequent usbfadm invocation
                #
                if [[ ${bootmode} = 'usbflash_arc' ]]; then
                    echo 'usbflash' > /boottmp/boot_mode
                fi

                echo "$devname" > /boottmp/boot_restore_devname
                echo "$uconf"   > /boottmp/boot_user_config
                sync
                sleep 5
                closenotice "done: sync current ${memfstype} as $uconf"
            else
                echoerr
                echoerr "sync: $devname isn't for FuguIta's backup."
                echoerr "sync: directory /livecd-config doesn't exist"
            fi
            umount $devname
        fi
    fi
}

#-------------------
# archive - archive synced data to *.cpio.gz
#
cmd_archive () {
    # some checks
    #
    echo

    # Is device name set?
    if [[ -z "$devname" ]]; then
        echoerr "Name of device not set. Use 'target' to perform this."
        return 1
    fi

    # Is data set name set?
    if [[ -z "$uconf" ]]; then
        echoerr "Name of saving data not set. Use 'saveas' to perform this."
        return 1
    fi

    # Is device mountable?
    if ! mount -o async,noatime $devname $mntdir1; then
        echoerr "archive: can't mount $devname"
        return 1
    fi

    # Is device for FuguIta's usbfadm sync?
    if [[ ! -d $mntdir1/livecd-config ]]; then
        echoerr "archive: $devname isn't for FuguIta's backup."
        umount $devname
        return 1
    fi

    local arcbase=$mntdir1/livecd-config/$verarch

    # Is there directory for sync?
    if [[ ! -d $arcbase ]]; then
        echoerr "archive: directory $arcbase not found"
        umount $devname
        return 1
    fi

    local srcdir="$arcbase/$uconf"

    # Does source directory exists?
    if [[ ! -d "$srcdir" ]]; then
        echoerr "archive: directory $uconf not found"
        umount $devname
        return 1
    fi

    # set archive file name
    local destarc=$(rl_wread "Name of archive file" "${uconf}-$(date +%y%m%d).cpio.gz" '')
    destarc=$(echo $destarc | tr -cd 0-9A-Za-z.,_-+=:%@)  # omit invalid chars
    if [[ -z "$destarc" ]]; then
        echoerr "archive: no archive specified, exiting."
        umount $devname
        return 1
    fi
    destarc=${destarc%.cpio.gz}.cpio.gz  # ensure with file extension
    if [[ -e "$arcbase/$destarc" ]]; then
        if [[ $(ask_yn "file $destarc exists, proceed anyway?" n) != 1 ]]; then
            umount $devname
            return
        fi
    fi

    # perform archiving
    #
    notice "archive $uconf as $destarc"
    (if cd "$srcdir"; then
         echo 'measuring disk usage, please wait...'
         set -- $(du -sk "$srcdir")
         echo "${1}K: $uconf -> $destarc"
         pax -x cpio -wX -s '|^\./tmp/..*||' . | pv -s "${1}K" | gzip -9 > "$arcbase/$destarc"
         if [[ $? != 0 ]]; then
             echoerr $destarc not archived normally, abort
             rm -f "$arcbase/$destarc"
         fi
     fi)
    umount $devname
    closenotice "end of archiving"
}

#-------------------
# info - show status of storage device
#
cmd_info () {
    if [[ -n "$devname" ]] && mount -r $devname $mntdir1; then
        (cd $mntdir1/livecd-config
         echo
         df -hi $mntdir1
         echo "\nscanning...\n" && du -sh [1-9].[0-9]/*/* [1-9].[0-9]-*-*)  2>/dev/null
        umount $devname
    fi
}

#-------------------
# saveas - set data set name
#          default is our short hostname
#
cmd_saveas () {
    if [[ -n "devname" ]] && mount $devname $mntdir1; then
        [ -d $mntdir1/livecd-config/$verarch ] || mkdir -p $mntdir1/livecd-config/$verarch
        local confs=$(cd $mntdir1/livecd-config/$verarch && echo *)
        umount $devname
    fi
    [[ -z "$uconf" ]] && uconf=$(hostname -s)
    uconf=$(rl_wread "Name of saved data" $uconf $confs)
    uconf=$(echo $uconf | tr -cd 0-9A-Za-z.,_-+=:%@)  # strip invalid chars
    echo
    if [[ -z "$uconf" ]]; then
        echoerr "Unset the name of saving data"
    else
        echo "Your data will be saved as \"$uconf\"."
    fi
}

#-------------------
# target - set target device
#          for sync and expand
#
cmd_target () {
    local dev fs

    # search and set storage device
    #
    echo
    echo 'Searching storage device'
    echo 'Please make sure the device inserted.'
    [ $(wait_enter 'Then press ENTER') -lt 1 ] && return

    # search partitions for saved data
    #
    while :; do
        devname=''
        local devs_normal=' '
        local devs_crypto=' '
        # listing storage devices with investigation
        local rdev
        for rdev in $(sysctl -n hw.disknames \
                     | sed -E -e 's/:[0-9a-f]+/:/g; s/[:,]+/ /g;'); do

            # vnd included
            (expr $rdev : '^[sw]d[0-9]' || expr $rdev : '^vnd[0-9]' ) >/dev/null \
            || continue

            # # vnd execluded
            # expr $rdev : '^[sw]d[0-9]' >/dev/null || continue

            # $rdev is detected storage name - e.g. "sd2"
            local ptfs
            for ptfs in $(disklabel -c "$rdev" 2>/dev/null \
                         | awk '/^  [abd-p]:/ { print $1 $4 }'); do
                # $ptfs is "partition:fstype" - e.g. "d:4.2BSD"
                dev="$rdev"${ptfs%:*}
                fs=${ptfs#*:}
                case "$fs" in
                    RAID)
                        # check this RAID partition is crypto
                        if dd if=/dev/r$dev bs=1k count=9 2>/dev/null \
                           | fgrep -q 'SR CRYPTO'; then
                            devs_crypto="$devs_crypto$dev "
                            echo -n "?$dev "
                        else
                            echo -n "$dev "
                        fi
                        ;;
                    4.2BSD)
                        # check this FFS partition is for FuguIta's storage
                        if mount -r /dev/$dev $mntdir1 2>/dev/null; then
                            if [ -d $mntdir1/livecd-config ]; then
                                devs_normal="$devs_normal$dev "
                                local def_dev="$dev"
                                echo -n "+$dev "
                            else
                                echo -n "$dev "
                            fi
                            umount $mntdir1
                        else
                            echo -n "$dev "
                        fi
                        ;;
                esac
            done
        done

        # show result of scanning
        # then prompt to user
        #
        if [ "${devs_normal}${devs_crypto}" = '  ' ]; then
            echo
            echo 'No device available for saving data'
            break
        else
            echo
            dev=$(rl_wread "target device" "$def_dev" ${devs_normal}${devs_crypto})
            if [ 0 -lt $(expr "$devs_normal" : ".* $dev ") ]; then
                devname=/dev/$dev
                break
            elif [ 0 -lt $(expr "$devs_crypto" : ".* $dev ") ]; then
                bioctl -c C -l /dev/$dev softraid0
                sleep 2  # wait for kernel message displayed
            else
                break
            fi
        fi
    done
}

#-------------------
# newdrive - remaster to another device
#
cmd_newdrive () {
    local newdev
    local vnfile

    echo
    if mount | grep -q '^\/dev\/[0-9a-z][0-9a-z]* on \/sysmedia type '
    then
        echo 'Please make sure the device inserted.'
        [ $(wait_enter 'Then press ENTER') -lt 1 ] && return
    else
        echo 'Sorry, cannot find /sysmedia.  You cannot do newdrive with this boot mode.'
        return 1
    fi

    echo
    echo '==== disk(s) and vnode devices  ============================'
    dmesg | sed -e '/^[sw]d[0-9][0-9]*[ :]/!d; s/> .*/>/'
    if vnconfig -l | grep -q '^vnd[0-3]: not in use'; then
        newdev=img
        echo "\n$newdev: create FuguIta raw disk image on current directory"
    else
        newdev=''
    fi
    echo '============================================================'
    newdev=$(rl_wread "Enter the name of device which FuguIta will be installed" "" $(sysctl -n hw.disknames|tr :, \\\012|grep '^[sw]d[0-9]$') $newdev)
    if [ -z "$newdev" ]; then
        echoerr
        echoerr "newdrive: no device name"
        return 1
    elif [ 0 -eq $(expr "$newdev" : '^[sw]d[0-9]$') ] && \
       [ 0 -eq $(expr "$newdev" : '^vnd[0-9]$') ] && \
       [ 0 -eq $(expr "$newdev" : '^img$') ]; then
        echoerr
        echoerr "newdrive: device $newdev is not supported."
        return 1
    fi

    if mount | grep -q "^/dev/$newdev"; then
        echoerr
        echoerr "newdrive: $newdev is already mounted."
        return 1
    fi

    if swapctl -l | grep -q "^/dev/$newdev"; then
        echoerr
        echoerr "newdrive: $newdev is being used for swap."
        echoerr "newdrive: Consider 'swapctl -d' for deactivate the swap partition."
        return 1
    fi

    # create vnode device file if specified
    #
    if [[ "$newdev" = 'img' ]]; then
        vnfile=FuguIta-$(</usr/fuguita/version).img
        if [ -e "$vnfile" ]; then
            echo
            [ 1 -gt $(ask_yn "Image file $vnfile already exists\nRemove it and proceed?" r) ] && break
        fi
        while :; do
            echo
            echo "Enter size of a vnode device file."
            echo "You can add suffix K, M, G or T (otherwise considered 'bytes')."
            local ans=$(rl_wread '' '')
            echo
            if [ 1 -lt $(expr "X$ans" : 'X[0-9][0-9]*') ]; then
                sect_size=$((1024*1024)) # block size for dd
                [ $(byte2sect 724M) -le $(byte2sect $ans) ] && break
                [ 1 -le $(ask_yn "$ans is very small value.\nDo you really take this value?" n) ] && break
            fi
        done

        local sects=$(byte2sect $ans)
        if which pv >/dev/null 2>&1 ; then
            dd if=/dev/zero bs=$sect_size count=$sects | pv --size $((sect_size*sects)) > $vnfile \
            && newdev=$(vnconfig $vnfile)
        else
            (while :; do sleep 15; pkill -INFO dd || exit 0; done) &  # to display progress
            dd if=/dev/zero of=$vnfile bs=$sect_size count=$sects \
            && newdev=$(vnconfig $vnfile)
        fi

        if [ -z "$newdev" ]; then
            echoerr "newdrive: sorry, cannot allocate a new vnode device."
            clear_vn $newdev $vnfile
            return 1
        fi
    fi

    echo
    if ! fdisk $newdev; then
        clear_vn $newdev $vnfile
        return 1
    fi

    if [ $(fdisk $newdev | grep -c ' Unused$') != 4 ]; then
        echo
        echo 'This disk seems to have been partitioned already.'
        if [ $(ask_yn "Continue anyway?" n) -lt 1 ]; then
            clear_vn $newdev $vnfile
            return
        fi
    fi

    # get disk parameters
    #
    eval $(disk_scan $newdev)
    if [ ! "$diskscan" = ok ]; then
        echoerr "cannot get parameters of $newdev"
        clear_vn $newdev $vnfile
        return 1
    fi
    #
    # from here, $scandev is used as target device name instead of $newdev
    #

    # disable boot items if specified
    #
    local legacyitem='Legacy BIOS'
    case "$disable_legacyboot" in [Yy][Ee][Ss]) legacyitem='' ;; esac

    local uefiitem='UEFI'
    case "$disable_uefiboot" in [Yy][Ee][Ss]) uefiitem='' ;; esac

    local hybriditem=''
    [ "$legacyitem$uefiitem" = 'Legacy BIOSUEFI' ] && hybriditem='Hybrid'

    #-------------------
    # ask boot method and partition type
    #
    if [ $sects_total -lt $(byte2sect 2T) ]; then
        echo
        instsys=$(ask_which "Select boot method:" 'UEFI' "$legacyitem" "$uefiitem" 'none (only for save data)' "$hybriditem")

        echo
        case $instsys in
            Legacy)
                parttype=MBR
                ;;
            UEFI)
                parttype=$(ask_which "Select partition type:" MBR MBR GPT)
                ;;
            none)
                parttype=$(ask_which "Select partition type:" MBR MBR GPT Hybrid)
                ;;
            Hybrid)
                cat <<EOT
Notice:
You have selected 'Hybrid' as boot type.

This can be booted from either Legacy BIOS or UEFI.
And has two partition tables both MBR and GPT.

This is NOT normal configuration.
Modifying partition table later may cause any problem.

EOT
                if [ 1 -gt $(ask_yn "Proceed anyway?" n) ]; then
                    clear_vn $newdev $vnfile
                    return
                fi
                parttype=Hybrid
                ;;
            *)
                echo "Select one of above."
                clear_vn $newdev $vnfile
                return 1
                ;;
        esac
        if [ -z "$parttype" ]; then
            echo "Select one of above."
            clear_vn $newdev $vnfile
            return 1
        fi
    else
        echo
        instsys=$(ask_which "Select boot method:" '' "$uefiitem" 'none (only for save data)')
        parttype=GPT
    fi

    #-------------------
    # calculate sectors
    #
    ps_hgap=$disk_head_gap
    ps_uefi=0
    ps_fisys=0
    ps_fiswp=0
    ps_fidat=0
    ps_fat=0
    case "$parttype" in
        GPT|Hybrid)
            ps_tgap=$ps_hgap ;;  # for 2nd GPT
        *)
            ps_tgap=0 ;;
    esac

    case "$instsys" in
        Legacy)
            ps_fisys=$(byte2sect "${fuguita_sys_mb}M")
            ;;
        UEFI|Hybrid)
            ps_uefi=$(byte2sect "${fuguita_uefi_kb}K")
            ps_fisys=$(byte2sect "${fuguita_sys_mb}M")
            ;;
        none)
            ;;
        *)
            echo "Select one of above."
            clear_vn $newdev $vnfile
            return 1
            ;;
    esac
    ps_free=$((sects_total-(ps_hgap+ps_uefi+ps_fisys+ps_tgap)))

    # change mem-based fs?
    #
    unset memfs_new
    if [ $instsys != none ]; then
        echo
        # want prompt values with upper case
        # and its string must be lower
        memfs_new=$(ask_which 'Type of /ram:' MFS MFS TMPFS | tr A-Z a-z)
    fi

    #-------------------
    # ask and calculate User's data area
    # which is prepared as partition d
    #
    while :; do
        echo
        echo "Enter sizes for swap, user data and extra FAT."
        echo "  You can add suffix K, M, G or T (otherwise considered 'bytes')."
        echo "  '*' implies 'all'"
        echo "  '0' doesn't make this partition."
        echo
        echo "$(sect2byte $ps_free) ($(sect2byte $ps_free 2)) (${ps_free}sectors) free"

        ans=$(rl_wread 'swap' "$newdrive_defswap" "$newdrive_defswap")
        echo
        if [ 1 -lt $(expr "X$ans" : 'X[0-9][0-9]*') ]; then
            ps_fiswp=$(byte2sect "$ans")
            if [ $ps_free -lt $ps_fiswp ]; then
                echo 'That size exceeds the limit.'
                return 1
            fi
        elif [ "X$ans" = 'X*' ]; then
            ps_fiswp=$ps_free
            ps_free=0
            break
        else
            echoerr "$ans ... What?"
            continue
        fi

        ps_free=$((ps_free-ps_fiswp))

        echo
        echo "$(sect2byte $ps_free) ($(sect2byte $ps_free 2)) (${ps_free}sectors) free"
        ans=$(rl_wread 'user data' '*')
        echo
        if [ 1 -lt $(expr "X$ans" : 'X[0-9][0-9]*') ]; then
            ps_fidat=$(byte2sect "$ans")
            if [ $ps_free -lt $ps_fidat ]; then
                echo 'That size exceeds the limit.'
                return 1
            elif [ 0 -gt $ps_fidat -a $ps_fidat -lt $(byte2sect 16M) ]; then
                if [ 1 -le $(ask_yn "$ans ($ps_fidat sectors) is very small value.\nDo you really take this value?" n) ]; then
                    break
                else
                    return 1
                fi
            else
                ps_free=$((ps_free-ps_fidat))
            fi
            break
        elif [ "X$ans" = 'X*' ]; then
            ps_fidat=$ps_free
            ps_free=0
            break
        else
            echoerr "$ans ... What?"
            continue
        fi
    done

    #  Is FuguIta's Data Store encrypted?
    #
    if [ 0 -lt $ps_fidat ]; then
        if [ 1 -le $(ask_yn "user data encryption?" n) ]; then
            enc_fidat='yes'
            cat <<EOT

Enter passphrase twice. They'll be not echoed.

//// CAUTION ////////////////////////////
////   If you lost this passphrase,
////   you'll never access ${scandev}d.
/////////////////////////////////////////

EOT
            stty -echo; 
            echo -n 'Passphrase:'; read enc_pp1; echo  # note: enc_pp1 is global
            local enc_pp2                              #       enc_pp2 is local
            echo -n 'Passphrase:'; read enc_pp2; echo
            stty echo
            if [ ! "$enc_pp1" = "$enc_pp2" ]; then
                echo 'passphrase not matched'
                clear_vn $newdev $vnfile
                unset enc_pp1 enc_pp2
                return 1;
            fi
            unset enc_pp2
        else
            enc_fidat='no'
        fi
    fi

    # set unused area to FAT?
    #
    if [ 0 -lt $ps_free ]; then
        echo
        if [ 1 -le $(ask_yn "Create an extra FAT partition?" r) ]; then
            ps_fat=$ps_free
            ps_free=0
        fi
    fi

    echo
    showparams

    echo
    echo '***THIS IS THE LAST CHANCE***'
    echo "If you type 'Y' now, all the data on $scandev will be lost."
    if [ $(ask_yn "Are you sure to modify disk ${scandev}?" n) -lt 1 ]; then
        clear_vn $newdev $vnfile
        return
    fi

    #-------------------
    # perform disk modifications
    #
    case "$parttype" in
        MBR|GPT)
            zerofill_head
            fdisk_init $parttype
            setup_fs
            ;;
        Hybrid)
            zerofill_head
            # first, create MBR partitions including UEFI Sys (ID=EF)
            fdisk_init $parttype
            setup_fs

            # save sector 0 (MBR) to a file
            dd if=/dev/r${scandev}c of=$lockdir/hybrid.mbr bs=$sect_size count=1

            # next, create a GPT with same contents as MBR's one
            # (also UEFI bootloader installed)
            fdisk_init GPT

            # install MBR boot loader, too
            if [ "$instsys" = Hybrid \
                   -a $(expr "$disable_uefiboot" : '[Yy][Ee][Ss]') -eq 0 ]; then
                notice "Installing UEFI boot loader"
                if mount -t msdos -o -l /dev/${scandev}i $mntdir1; then
                    mkdir -p $mntdir1/efi/BOOT
                    cp /fuguita/usr/mdec/*.EFI $mntdir1/efi/BOOT
                    umount $mntdir1
                fi
            fi

            # overwrite protective MBR with saved MBR
            dd if=$lockdir/hybrid.mbr of=/dev/r${scandev}c
            rm $lockdir/hybrid.mbr
            ;;
        *)
            echoerr "Partition type: '$parttype' - unknown"
            ;;
    esac

    # site-specific post processing
    #
    [ -r /etc/fuguita/usbfadm_postproc.sh ] && . /etc/fuguita/usbfadm_postproc.sh

    if [ "$debugfile" ]; then
        { echo
          echo "////////// End of newdrive ////////////////////////////////////////"
          showparams verbose
        } >> "$debugfile"
    fi

    if [[ -n "$vnfile" ]]; then
        clear_vn $scandev  # don't remove vn img file
    fi
}

#-------------------
# expand - expand targeted storage partition to fill the device
#
cmd_expand () {
    local expcmd expdev expbuid exppar expsect exptype
    local fdisk_input fdisk_output
    local gpt_fix_cmds gpt_orig

    echo
    if [[ -z "$devname" ]]; then
        echoerr "Name of device not set. Use 'target' to perform this."
        return
    fi

    expdev=${devname#/dev/}  # expdev initially e.g. 'sd2d'
    exppar=${expdev#*[0-9]}  # partition to expand - 'd'
    expdev=${expdev%?}       # device to expand - 'sd2'

    eval $(disk_scan $expdev)  # to get sect_size for byte2sect
    [[ $diskscan != 'ok' ]] && return 1

    fdisk_output=$(fdisk -v $expdev)

    # check partitioning types
    #
    if echo "$fdisk_output" | grep -q '^Disk: .* Usable LBA: '; then
        exptype=gpt  # GPT with ESP
    elif echo "$fdisk_output" | grep -q 'Signature: 0xAA55'; then
        if echo "$fdisk_output" | grep -q '[0123]: EF .* EFI Sys'; then
            if echo "$fdisk_output" | grep -q '[0123]: EE .* EFI GPT'; then
                exptype=hybrid    # MBR and GPT co-exist
            else
                exptype=mbr_uefi  # MBR with UEFI bootloader
            fi
        else
            exptype=mbr
        fi
    else
        exptype=unknown  # random stuff
    fi

    # Get OpenBSD's partition ID
    #
    local partid_obsd=''
    case "$exptype" in
        "mbr"|"mbr_uefi")
            partid_obsd=$(echo "$fdisk_output" | tr -d '*:' | awk '/A6.*OpenBSD/ {print $1}')
            fdisk_input="e ${partid_obsd}\nA6\nn\n\n*\nw\nq\n"
            ;;
        "gpt")
            partid_obsd=$(echo "$fdisk_output" | sed '/^Primary GPT:/,/^Secondary GPT:/!d' | awk '/[0-9]: OpenBSD/{print $1+0}')
            fdisk_input="e ${partid_obsd}\nA6\nn\n\n*\nw\nq\n"
            ;;
        "hybrid")
            echoerr "expand: $expdev - cannot modify Hybrid MBR"
            echoerr "expand: Please consider running newdrive against another device."
            return 1
            ;;
        "unknown")
            echoerr "expand: cannot read any partition tables - $expdev"
            return 1
            ;;
    esac

    if [[ -z "$partid_obsd" ]]; then
        echoerr "expand: OpenBSD partition not found"
        return 1
    fi

    # check if another fdisk partition exists after a partition in OpenBSD
    # ... if so, cannot be expanded (may be unbootable)
    # 
    if [[ "YES" == $(echo "$fdisk_output" | awk 'BEGIN { maxstart=-1 }  # max start sector

                                                 # determine type of partition table
                                                 /Usable LBA: [0-9]/ { tbtype = "gpt"; next }
                                                 /geometry: [0-9]/   {
                                                     if (tbtype != "gpt") {
                                                         tbtype = "mbr"
                                                     }
                                                     next
                                                 }

                                                 # OpenBSD on MBR
                                                 tbtype == "mbr" && /^.[0123]: A6 / {
                                                     obsdstart = $11 + 0
                                                     next
                                                 }
                                                 # other on MBR
                                                 tbtype == "mbr" && /^.[0123]: [0-9A-F][0-9A-F] / {
                                                     if (maxstart < $11 + 0)
                                                         maxstart = $11 + 0
                                                     next
                                                 }

                                                 # OpenBSD on GPT
                                                 tbtype == "gpt" && /^ [ 12][ 0-9][0-9]: OpenBSD/ {
                                                     obsdstart = $(NF-2) + 0
                                                     next
                                                 }
                                                 # other on GPT
                                                 tbtype == "gpt" && /^ [ 12][ 0-9][0-9]: / {
                                                     if (maxstart < $(NF-2) + 0)
                                                         maxstart = $(NF-2) + 0
                                                 }
                                                 END {
                                                     # exist other partition after OpenBSD?
                                                     if (obsdstart<maxstart)
                                                         print "YES"
                                                     else
                                                         print "NO"
                                                 }') ]]; then
        echo "$fdisk_output"
        cat <<EOT

There is another fdisk partition after the OpenBSD partition.
If you run expand in this state, partitions may overlap and the system may not boot.
Rather than expanding the partition on this flash device, consider using the newdrive subcommand to build your FuguIta system on another device.
EOT
        return 1
    fi

    expcmd=$(ask_which "Select the expansion method for ${expdev}${exppar}:" \
                       'exit without expansion' \
                       'growfs - expands the partition while retaining its contents' \
                       'newfs - expand and format the partition' \
                       'exit without expansion')

    case "$expcmd" in
        growfs)
            echo
            echo "This expands ${expdev}${exppar} as large as possible."
            echo 'It may take time.'
            echo 'It is recommended that you make a backup of your data before proceeding'
            echo 'with the subsequent expansion process.'
            [[ $(ask_yn 'Do you proceed?' n) != 1 ]] && return
            ;;
        newfs)
            echo
            echo "This makes ${expdev}${exppar} as large as possible."
            echo "Note that all contents in ${expdev}${exppar} will be removed"
            [[ $(ask_yn 'Do you proceed?' n) != 1 ]] && return
            ;;
        exit)
            return
            ;;
    esac

    echo

    # fix GPT header
    #   generate fdisk commands to fix GPT
    #   then send them to fdisk -e
    #
    if [[ "$exptype" = 'gpt' ]]; then
        gpt_orig=$(fdisk -v $expdev)
        gpt_fix_cmds=$(echo "$gpt_orig" | fixgpt)

        # error check
        #   fixgpt outputs 'abort' without 'write'
        #   when error occurred internally
        #
        if ! echo "$gpt_fix_cmds" | grep -q '^write$'; then
            echoerr 'expand: unable modify GPT, expand aborted'
            return 1
        fi

        # mofify GPT
        #
        if ! echo "$gpt_fix_cmds" | fdisk -e $expdev; then
            echoerr 'expand: error occurred at mofifying GPT'
            echoerr 'expand: So GPT info before modification saved as "OriginalGPT" at current directory'
            echoerr 'expand: Please check GPT, then recover GPT manually, if needed'
            echo "$gpt_orig" > OriginalGPT.${RANDOM}
            return 1
        fi
    fi

    # expand fdisk or GPT partition
    echo "$fdisk_input" | fdisk -e $expdev > /dev/null

    # expand disklabel partition
    echo "b\n\n*\nm ${exppar}\n\n*\n\nw\nq\n" | disklabel -c -E ${expdev}${exppar} > /dev/null

    # setup the filesystem
    #
    case "$expcmd" in
        growfs)
            growfs -y /dev/r${expdev}${exppar}
            fsck -fy /dev/r${expdev}${exppar}
            ;;
        newfs)
            # get number of sectors of the expaneded partition
            expsect=$(disklabel -c ${expdev} | awk "\$1 == \"${exppar}:\" { print int(0.5+\$2) }" )

            if [ $expsect -lt $(byte2sect 2G) ]; then
                # adjust inode density for little space
                # because too many symlinks when booted mode 0
                newfs -O 1 -m 0 -o space -i1024 -b4096 -f512 /dev/r${expdev}${exppar}
            elif [ $expsect -lt $(byte2sect 4G) ]; then
                newfs -O 2 -m 0 -o space /dev/r${expdev}${exppar}
            elif [ $expsect -lt $(byte2sect 8G) ]; then
                newfs -O 2 -m 0 /dev/r${expdev}${exppar}
            else
                newfs -O 2 -m 0 -o time /dev/r${expdev}${exppar}
            fi

            fsck -fy /dev/r${expdev}${exppar}
            if mount /dev/${expdev}${exppar} $mntdir1; then
                local expduid=$(dev2duid $expdev)  # get DUID after disk_scan is OK
                mkdir -p $mntdir1/livecd-config/${verarch}
                local umem="75%"
                [[ "$memfstype" = 'tmpfs' ]] && umem=0
                cat <<EOT >$mntdir1/livecd-config/${verarch}/noasks
#
# noasks - parameter settings for non-interactive boot
#
# Make statements uncommented
# Then assign real values
#
#
# FuguIta system device
#${expduid:+   - Use one of two lines}
#noask_rdev=${expdev}a  # device name format
#${expduid:+noask_rdev=${expduid}.a  # DUID format}
#
# max ${memfstype} size
#noask_umem=${umem}
#
# boot mode
#noask_setup_rw_mode=3
#
# storage device
#${expduid:+   - Use one of two lines}
#noask_confdev=${expdev}${exppar}
#${expduid:+noask_confdev=${expduid}.${exppar}  # DUID format}
#
# data set name in USB flash drive
#noask_confdata=$(hostname -s)
EOT
                umount $mntdir1
            fi
            ;;
        *)
            return
            ;;
    esac
}


#==================================
# Active Code from HERE.
#==================================

#-------------------
# systemwide constants
#
readonly lockdir=/usbfadm.d
readonly mntdir1="$lockdir/mnt1"
readonly mntdir2="$lockdir/mnt2"
readonly   osrel=$(sysctl -n kern.osrelease)
readonly   hwmac=$(sysctl -n hw.machine)
readonly verarch=${osrel}/${hwmac}

#-------------------
# environment check
#
if [ ! -r /usr/fuguita/version ]; then
    echoerr "You are not running FuguIta."
    exit 1
fi

if [ ! $(id -un) = root ]; then
    echoerr "${0##*/} must be run as a root."
    exit 1
fi

# check for cwd
#
if ! pwd >/dev/null 2>&1; then
    # in case of cwd already unmounted
    cd /
fi
if [ 1 -le $(expr X"$(pwd)" : X$mntdir1) ]; then
    echo
    echoerr 'You are under $mntdir1. Please move to other directory.'
    exit 1
fi

umask_o=$(umask); umask 077
if mkdir $lockdir 2>/dev/null; then
    mkdir $mntdir1
else
    umask $umask_o
    echoerr "another ${0##*/} running (or remove $lockdir)"
    exit 1
fi
umask $umask_o

#-------------------
# initializations
#
trap 'echo ${0##*/}: interrupted. >&2; clear_exit 1' INT

# read status files
#
[ -r /boottmp/boot_mode ]            && bootmode=$(cat /boottmp/boot_mode)
[ -r /boottmp/boot_restore_devname ] && devname=$(cat /boottmp/boot_restore_devname)
[ -r /boottmp/boot_user_config ]     && uconf=$(cat /boottmp/boot_user_config)

# to avoid accidentally overwriting past archive files with current
# sync data
if [[ "$bootmode" = 'usbflash_arc' ]]; then
    unset uconf
fi

# systemwide defaults
#
  disk_head_gap=64
 fuguita_sys_mb=1024
fuguita_uefi_kb=512
     enc_ppfile="$lockdir/usbfadm_pp"
      memfstype='mfs'  # mfs or tmpfs

#
# overwrite defaults
#
if [[ -r /etc/fuguita/global.conf ]]; then
    . /etc/fuguita/global.conf
fi


# command line arguments
#
cmdargs=$(getopt qritdh $*)
set -- $cmdargs
while [ 1 -lt $# ]; do
    case "$1" in
        -r) opt_mode=resync; shift;;
        -i) opt_mode=info;   shift;;
        -q) opt_quiet=yes;   shift;;
        -t) opt_trace=yes; set -x; shift;;
        -d) debugfile=$(pwd)/usbf.debugout  # This is also flag.
            shift;;
        *) cat <<EOT 2>&1
Usage: ${0##*/} [-riqtdh]

    -r : redo sync non-interactively
        (must run 'sync' at interactive mode before doing this)
    -i : show info about the persistent storage
    -q : quiet mode when redo sync
    -t : trace output (pass -x to shell)
    -d : debug output for newdrive to file 'usbf.debugout'
    -h : print this help
EOT
           clear_exit 0
           ;;
    esac
done
[ "$debugfile" ] && rm -f "$debugfile"

#-------------------
# non-interactive process
#
case "$opt_mode" in
    resync)
        if [ -z "$devname" ]; then
            echoerr "${0##*/}: Name of device isn't set. Use 'target', then 'sync' in interactive mode."
            clear_exit 1
        elif [ -z "$uconf" ]; then
            echoerr "${0##*/}: Name of saving data isn't set. Use 'saveas', then 'sync' in interactive mode."
            clear_exit 1
        fi

        if mount -o async,noatime $devname $mntdir1; then
            if [ "$opt_quiet" = 'yes' ]; then
                rsync -q -rlptgo --devices -xHS --delete --include '*/tmp' --exclude 'tmp/*' /ram/. $mntdir1/livecd-config/$verarch/$uconf/.
                retval=$?
            else
                notice "sync ${devname} with current ${memfstype} as ${uconf}"
                rsync --progress -rlptgo --devices -xHS --delete --include '*/tmp' --exclude 'tmp/*' /ram/. $mntdir1/livecd-config/$verarch/$uconf/.
                retval=$?
                closenotice "done: sync ${devname} with current ${memfstype} as ${uconf}"
            fi
            # find $mntdir1 \! -type d \! -type f \! -type l -print | xargs rm -f
            rm -rf $mntdir1/livecd-config/$uconf/tmp/{.??*,*}
            sync
            sleep 5
            umount $devname
            clear_exit $retval
        else
            clear_exit 1
        fi
        ;;
    info)
        cat <<EOT
FuguIta's persistent storage status:

 Version/Arch: $verarch  (FuguIta-$(cat /usr/fuguita/version))
    Boot mode: $bootmode
Target device: ${devname:-not set}
Data saved as: ${uconf:-not set}

EOT
        if [[ -n "$devname" ]] && mount -r $devname $mntdir1; then
            (cd $mntdir1/livecd-config
             echo
             df -hi $mntdir1
             echo "\nscanning...\n" && du -sh [1-9].[0-9]/*/* [1-9].[0-9]-*-*) 2>/dev/null
            umount $devname
        fi
        clear_exit $?;
        ;;
esac

#-------------------
# interactive process
#   banner and command loop
#
d=$devname ; [ -z "$devname" ] && d='not set'
u=$uconf   ; [ -z "$uconf" ]   && u='not set'
cat <<EOT

Welcome to usbfadm.
USB flash drive administration tool for FuguIta

 Version/Arch: $verarch  (FuguIta-$(cat /usr/fuguita/version))
    Boot mode: $bootmode
Target device: $d
Data saved as: $u
EOT

echo
# check if rlwrap is available
#   When control tty is missing (in /etc/rc.shutdown for example),
#   rlwrap in command substitution "$(rlwrap ...) " fails.
if retval=$(rlwrap true) 2>/dev/null; then
    echo "readline capability available"
    echo "TAB to complete the reserved words"
else
    echo "Sorry, readline capability unavailable"
fi

echo
echo 'Type ? for help.'

while :; do
    #-------------------
    # setup prompt string
    #
    d="${devname#/dev/}" ; [ -z "$devname" ] && d='?'
    u="$uconf"           ; [ -z "$uconf" ]   && u='?'
    echo
    cmd=$(rl_wread "$d : $u " '' quit bye exit sync archive info saveas target newdrive expand help ?)

    set X $cmd

    #-------------------
    # process every command
    #
    case "$2" in
        #-------------------
        # finish all
        #
        q|quit|bye|exit|EOF|RL_ERR)
        echo
        echo 'Bye bye...'
        break;
        ;;

        sync)     cmd_sync;;
        archive)  cmd_archive;;
        info)     cmd_info;;
        saveas)   cmd_saveas;;
        target)   cmd_target;;
        newdrive) cmd_newdrive;;
        expand)   cmd_expand;;

        #-------------------
        # null command
        # ... only RET
        '')
        : # do nothing
        ;;

        #-------------------
        # other strings are invalid
        # then for help message
        #
        *)
        echo
        cat<<EOT
Interactive commands are;
    target    -  set the partition for sync, info and expand
    saveas    -  set the name of the data to be saved
    sync      -  sync the target with the current ${memfstype}
    archive   -  archive saved directory to *.cpio.gz
    info      -  show info about the target partition
    newdrive  -  make a new FuguIta LiveUSB
    expand    -  expand the target partition as large as possible
    bye, exit, quit
              - end of this utility

Command line options are;
    -r : redo sync non-interactively
        (must run 'sync' at interactive mode before doing this)
    -i : show info about the persistent storage
    -q : quiet mode when redo sync
    -t : trace output (pass -x to shell)
    -d : debug output for newdrive to file 'usbf.debugout'
    -h : print this help
EOT
        ;;
    esac
done

clear_exit 0
