#!/boottmp/sh

#========================================
#
# fiupdate - live updater for FuguIta
# KAWAMATA, Yoshihiro / kaw@on.rim.or.jp
# $Id: fiupdate,v 1.29 2025/01/01 00:58:54 kaw Exp $
#
#========================================

# Copyright (c) 2020--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.

#===================
# Utility functions
#===================

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

#-------------------
# ask user yes or no
# outputs answer to stdout
#
#     usage: ask_yn prompt yn
#
#       yn ...    y: default is yes
#                 n: default is yes
#                 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
        echo2 -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
}

# display usage
#
usage () {
    cat <<EOT >&2
Usage: ${PROGNAME} yyyymmddn

You should locate FuguIta-${REL}-${ARCH}-yyyymmddn.iso.gz
and either SHA256 or MD5 at current directory before invoke this.
EOT
}

# check existing kernels/ffsimg
#
check_kern_ffsimg () {
    readonly FFSIMG=/sysmedia/fuguita-${REL}-${ARCH}.ffsimg
    readonly BSDFI=/sysmedia/bsd-fi
    readonly BSDFI_MP=/sysmedia/bsd-fi.mp

    if [ ! -f $FFSIMG ]; then
        echo2 "$FFSIMG not found (other version/arch ?)"
        ls -l /sysmedia >&2
        exit 1
    fi

    if [ ! -f $BSDFI ]; then
        echo2 "$BSDFI not found (device for saving data only?)"
        ls -l /sysmedia >&2
        exit 1
    fi

    if [ ! -f $BSDFI_MP ]; then
        echo2 "$BSDFI_MP not found (device for saving data only?)"
        ls -l /sysmedia >&2
        exit 1
    fi
}

# check mount status
#
check_mnttype () {
    local mount_out=$(mount)  # cache mount's output
    local mnttype=unknown

    if echo "$mount_out" | grep -q '^/dev/.* on /sysmedia-iso type '; then
	if echo "$mount_out" | grep -q '/sysmedia-iso type ntfs'; then
            mnttype=sys-iso-ntfs  # NTFS with only R/O, can't update
	else
            mnttype=sys-iso
	fi
    elif echo "$mount_out" | grep -q '^/dev/cd.* on /sysmedia type '; then
        mnttype=livedvd
    elif ! echo "$mount_out" | grep -q '^/dev/.* on /sysmedia type '; then
        mnttype=on-memory
    else
        mnttype=liveusb
    fi

    echo $mnttype
}

# decompress ISO image
#
decompress_iso () {
    rm -f ${FI}.iso
    echo2
    echo2 "decompressing ${FI}.iso.gz..."
    if ! pv ${FI}.iso.gz | gzip -d -o ${FI}.iso; then
        rm -f ${FI}.iso
        exit 1
    fi
}

# setup update environment
#   mount *.ISO as a vnode device
#
setup_iso_content () {
    # mount vnode device
    mkdir ${FI}
    vnconfig vnd0 ${FI}.iso
    mount -r /dev/vnd0a ${FI}
}

# copy executables these are needed to overwrite filesystem image
# to TMPFS
#
setup_rampath () {
    mkdir -p /ram/fiupdate.bin
    cp -p /bin/{cat,echo,mv,rm,rmdir,sync} \
          $(which pv) \
       /ram/fiupdate.bin/.

    PATH=/boottmp:/ram/fiupdate.bin:$PATH
    export PATH
}

# comfirm update to an user
#
confirm_exec () {
    cat <<EOT

Now ready to update FuguIta-$(cat /usr/fuguita/version) to ${FI}.

This machine will reboot immediately after update completed.

EOT
    local ans="$(ask_yn "Do you proceed?" n)"
    echo2
    if [ 1 -eq $ans ]; then
        trap "" INT QUIT  # We can't abort following process
    else
        cleanups
        exit
    fi
}

# stop all daemons
#
stop_all_srvs () {
    # first argument (keep_daemon)
    # if YES, don't do this sub
    [[ "$1" = "YES" ]] && return 0

    local daemon
    echo2 "stopping all daemons..."
    for daemon in $(rcctl ls started); do
        rcctl stop $daemon
    done
    sleep 5  # not to proceed if session broken
    echo2
}

# rewrite kernels and ffsimg at mode 0 or 1
#
update_liveusb () {
    echo2 "overwriting uniprocessor kernel..."
    pv ${FI}/bsd-fi > $BSDFI

    echo2 "overwriting multiprocessor kernel..."
    pv ${FI}/bsd-fi.mp > $BSDFI_MP

    echo2 "overwriting filesystem image..."
    pv ${FI}/fuguita-${REL}-${ARCH}.ffsimg > $FFSIMG
}

# make disk/partition list, then echo to stdout
#
list_diskpart () {
    local disk part part_list
    for disk in $(sysctl -n hw.disknames|tr , ' '); do
        # fd* and cd* are not needed
        # especially disklabel for fd* will never return
        case "$disk" in
            wd*|sd*)
                for part in $(disklabel ${disk%:*} 2>/dev/null \
                              | sed -e "/^  [ad-p]: /!d; s/^  \\([ad-p]\\):.*/${disk%:*}\1/"); do
                    part_list="${part_list:+$part_list }$part"
                done;;
        esac
    done
    echo $part_list
}

# find UNMOUNTED FuguIta's system disk/partition
#
find_free_fisys () {
    local part fisys
    for part in $(list_diskpart); do
        case "$part" in
            sd*|wd*)
                if mount -r /dev/$part /mnt 2> /dev/null; then
                    if [ -f /mnt/bsd-fi.mp ]; then
                        fisys="${fisys:+$fisys }$part"
                    fi
                    umount /dev/$part
                fi
                ;;
        esac
    done
    echo $fisys
}

# check if single FuguIta's boot device attached
# if success appropriate device set to global
# variable BOOTDEV
#
check_boot_fisys () {
    set -- $(find_free_fisys)
    if [ $# -le 0 ]; then
        echo2 "No FuguIta boot device found"
        echo2 "Please attach single FuguIta boot device."
        exit 1
    elif [ $# -eq 1 ]; then
        BOOTDEV="$1"
    elif [ $# -gt 1 ]; then
	cat <<EOT >&2
checked

Multiple FuguIta boot devices found: $*
Please attach single FuguIta boot device.
EOT
        exit 1
    else
        echo2 "can't happen in check_boot_fisys()"
        exit 1
    fi
}

# rewrite running ISO image,
# kernels used for boot and
# correnponding ffsimg
#
update_sys_iso () {
    # update ISO image file
    #
    echo2 "overwriting /sysmedia-iso/ISO/${FI}.iso ..."
    if ! pv ${FI}.iso > /sysmedia-iso/ISO/${FI}.iso; then
        rm -f /sysmedia-iso/ISO/${FI}.iso
        cleanups
        exit 1
    fi

    # update kernel and ffsimg at boot device
    #
    if mount /dev/$BOOTDEV /mnt; then
        if [ -f /mnt/bsd-fi ]; then
            echo2 "overwriting uniprocessor kernel..."
            pv ${FI}/bsd-fi > /mnt/bsd-fi
        fi

        if [ -f /mnt/bsd-fi.mp ]; then
            echo2 "overwriting multiprocessor kernel..."
            pv ${FI}/bsd-fi.mp > /mnt/bsd-fi.mp
        fi

        if [ -f /mnt/fuguita-${REL}-${ARCH}.ffsimg ]; then
            echo2 "overwriting filesystem image..."
            pv ${FI}/fuguita-${REL}-${ARCH}.ffsimg > /mnt/fuguita-${REL}-${ARCH}.ffsimg
        fi

        umount /mnt
    else
        cleanups
        exit 1
    fi
}

# rewrite kernels and ffsimg
# used for boot
#
update_boot_dev () {
    # update kernel and ffsimg at boot device
    #
    if mount /dev/$BOOTDEV /mnt; then
        if [ -f /mnt/bsd-fi ]; then
            echo2 "overwriting uniprocessor kernel - ${BOOTDEV}:/bsd-fi ..."
            pv ${FI}/bsd-fi > /mnt/bsd-fi
        fi

        if [ -f /mnt/bsd-fi.mp ]; then
            echo2 "overwriting multiprocessor kernel - ${BOOTDEV}:/bsd-fi.mp ..."
            pv ${FI}/bsd-fi.mp > /mnt/bsd-fi.mp
        fi

        if [ -f /mnt/fuguita-${REL}-${ARCH}.ffsimg ]; then
            echo2 "overwriting filesystem image - ${BOOTDEV}:/fuguita-${REL}-${ARCH}.ffsimg ..."
            pv ${FI}/fuguita-${REL}-${ARCH}.ffsimg > /mnt/fuguita-${REL}-${ARCH}.ffsimg
        fi

        umount /mnt
    else
        cleanups
        exit 1
    fi
}

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

# systemwide constants
#
readonly PROGNAME=${0##*/}
readonly REL=$(uname -r)
readonly ARCH=$(uname -m)

# greetings and notices
#
cat <<EOT

fiupdate - Live Updater for FuguIta LiveUSB
  Version/Arch: $REL/$ARCH  (FuguIta-$(cat /usr/fuguita/version))

EOT

if [ -z "$1" ]; then
    echo2 "$PROGNAME: version string not specified\n"
    usage
    exit
else
    readonly FI="FuguIta-${REL}-${ARCH}-${1}"
fi

# check environment
#
echo2    "Checking..."
echo2 -n "     environment: "
if [ ! "$(id -un)" = root ]; then
    # user priviledge
    echo2 "You are not root."
    exit 1
elif [ ! -e /usr/fuguita/version ]; then
    # distro
    echo2 "You are not running FuguIta."
    exit 1
elif [ ! -e ${FI}.iso.gz ]; then  # symlink is OK
    # downloaded file
    echo2 -n "${FI}.iso.gz not found"
    if [ -e ${FI}.img.gz ]; then
        echo2 -n " (NOT ${FI}.img.gz !)"
    fi
    echo2
    exit 1
else
    if [ -e SHA256 ]; then
        cksum_cmd=sha256
        cksum=SHA256
    elif [ -e MD5 ]; then  # symlink is OK
        cksum_cmd=md5
        cksum=MD5
    else
        echo2 "SHA256 or MD5 checksum not found"
        exit 1
    fi
fi

# Can I create *.iso here?
fs=$(fstat -p $$ | awk '$4=="wd" {print $5}')  # current file system
case "$fs" in
    /|/sysmedia*|/fuguita)
        echo2 "You can't run $PROGNAME at $(pwd) . Please change CWD."
        exit 1
        ;;
    *)
        if mkdir ${PROGNAME}_$$ 2>/dev/null; then
            rmdir ${PROGNAME}_$$
        else
            echo2 "You can't create a file at $(pwd) . Please change CWD."
            exit 1
        fi
        ;;
esac
   
# console devices
if [ 0 -lt $(expr $(tty) : '/dev/ttyp[0-9]') ]; then
    cat <<EOT >&2
checked

It seems you are running this script on X Window System,
via network or something like this.
In this situation, during update, corresponding processes
will be killed and then update may fail.

Running this on direct console device is highly recommended.

EOT

    if [ 1 -ne $(ask_yn "Continue anyway?" n) ]; then
       exit 1
    else
       # if continue, any daemon will not be stopped
       # before system update
       keep_daemon=YES
    fi
else
    keep_daemon=NO
    echo2 "ok"
    echo2
fi
cat <<EOT

Note: Use this software at YOUR OWN RISK.

      We recommend that you run this command in fresh boot (boot mode
      0, 1, or 2).
      Alternatively, you must quit all application software and save
      all your data before updating this FuguIta device.

      All daemons, including xenodm, will be stopped before the update.
      Please note that all X sessions will be aborted.

EOT

if [ 1 -ne $(ask_yn "Do you proceed?" n) ]; then
    exit
fi
echo

# check downloaded file
#
echo2    "Checking..."
echo2 -n "        checksum: "
$cksum_cmd -C $cksum ${FI}.iso.gz || exit 1

# check file layout
#
echo2 -n "     file layout: "
MNTTYPE=$(check_mnttype)
echo2 $MNTTYPE

# Do update by file layout
#
case $MNTTYPE in
    liveusb)
        echo2 -n "  existing files: "
        check_kern_ffsimg
        echo2 "ok"

        # define terminate session
        #
        cleanups () { mount -ur /sysmedia
                      umount -f ${FI}
                      rmdir ${FI}
                      vnconfig -u vnd0
                      rm ${FI}.iso
                      rm -rf /ram/fiupdate.bin; }

        decompress_iso
        setup_iso_content

        setup_rampath
        confirm_exec
        
        if ! mount -uw /sysmedia; then
            cleanups
            exit 1
        fi
        stop_all_srvs $keep_daemon
        update_liveusb

        # detach devices
        #
        umount -f /dev/vnd5a  # vnd5 is /sysmedia/fuguita-*.ffsimg
        vnconfig -u vnd5      # which is mounted on /fuguita

        umount -f /sysmedia
        ;;
    livedvd)
        echo2 "      media type: We can't rewrite LiveDVD. Burn another one to update."
	exit 1
        ;;
    sys-iso)
        # define terminate session
        #
        cleanups () { mount -ur /sysmedia-iso
                      umount -f ${FI}
                      rmdir ${FI}
                      vnconfig -u vnd0
                      rm ${FI}.iso
                      rm -rf /ram/fiupdate.bin; }

        echo2 -n "     boot device: "
        check_boot_fisys
        echo2 "/dev/$BOOTDEV - ok"

        decompress_iso
        setup_iso_content
        setup_rampath
        confirm_exec
        
        if ! mount -uw /sysmedia-iso; then
            cleanups
            exit 1
        fi
        stop_all_srvs $keep_daemon
        update_sys_iso

        # detach devices
        #
        umount -f /dev/vnd5a  # vnd5 is /sysmedia/fuguita-*.ffsimg
        vnconfig -u vnd5      # which is mounted on /fuguita

        umount -f /dev/vnd4a  # vnd4 is /sysmedia-iso/ISO/FuguIta-*-*.iso
        vnconfig -u vnd4      # which is mounted on /sysmedia

        umount -f /sysmedia-iso
        ;;
    sys-iso-ntfs)
        cat <<EOT >&2

We can't update because NTFS is mounted on /sysmedia-iso with read only.
Please locate the ISO file to \\ISO\\${FI}.iso on running Windows OS.
Sorry for inconvenience.
EOT
	exit 1
	;;
    on-memory)
        # define terminate session
        #
        cleanups () { umount -f ${FI}
                      rmdir ${FI}
                      vnconfig -u vnd0
                      rm ${FI}.iso; }

        echo2 -n "     boot device: "
        check_boot_fisys
        echo2 "/dev/$BOOTDEV - ok"

        decompress_iso
        setup_iso_content
        confirm_exec

        stop_all_srvs $keep_daemon
        update_boot_dev
        ;;
    *)
        echo2 "unknown file layout - $MNTTYPE (internal inconsistency?)"
	exit 1
	;;
esac
echo2
echo2 "update completed."

# not to perform usbfadm -r
#
if [ -f /etc/rc.shutdown ]; then
    mv /etc/rc.shutdown /etc/DISABLED.rc.shutdown
fi

# reboot ... this is it.
#
sync; sync; sync; sleep 5
echo2 "now rebooting..."
/boottmp/reboot
