#!/bin/sh
# Part of spindle http://asbradbury.org/projects/spindle
#
# See LICENSE file for copyright and license details

set -e

. ./config

run_qemu() {
  rm fifo.out fifo.in || true
  mkfifo fifo.out fifo.in
  qemu-system-arm -M versatilepb -cpu arm1136-r2 -m 256 -display none -no-reboot \
        -kernel zImage -hda qemu_rootfs.sqf -drive file=$1,index=1,media=disk,cache=unsafe \
        -drive file=wheezy_apt_cache.$IMGFORMAT,index=2,media=disk,cache=unsafe \
        -append "root=/dev/sda rw init=/sbin/init.sh panic=1 PATH=/bin:/sbin console=ttyAMA0 HOST=armv6l"\
        -net nic,model=rtl8139 -net user -redir tcp:22000::22 -daemonize -serial pipe:fifo \
        -pidfile qemu.pid
  sleep 10
}

ssh_in_to_qemu() {
  ssh -i qemu_arm_key -p 22000 -lroot localhost "$@"
}

scp_in_to_qemu() {
  scp -i qemu_arm_key -P 22000 "$1" root@localhost:"$2"
}

scp_r_from_qemu() {
  scp -r -i qemu_arm_key -P 22000 root@localhost:"$1" "$2"
}

onvm_chroot() {
  ssh_in_to_qemu chroot /mnt "$@"
}

shutdown_qemu() {
  ssh_in_to_qemu "sync && umount -a" || true
  echo "exit" > fifo.in
  sleep 5
  if [ -e qemu.pid ]; then
    QEMU_PID=$(cat qemu.pid)
    while [ -n "$QEMU_PID" ]; do 
      set +e
      kill -0 $QEMU_PID 2>/dev/null
      if [ $? -eq 0 ]; then
        printf "Qemu pid %s not finished yet. Waiting\n" "$QEMU_PID"
        sleep 1
      else
        QEMU_PID=""
      fi
      set -e
    done
  fi
  rm fifo.in
  rm fifo.out
#  sleep 15
}

attach_image_to_nbd() {
  # use -v as we seem to have problems otherwise...
  sudo qemu-nbd --nocache -v -c $2 $1 &
  sleep 5
}

detach_image_from_nbd() {
  sudo qemu-nbd -d $1
}

inspect_image() {
  cd work
  qemu-img create -f $IMGFORMAT -b ../$1 temp.$IMGFORMAT
  # Sigh http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=390433
  trap '[ -p fifo.in ] && shutdown_qemu' EXIT
  trap '[ -p fifo.in ] && shutdown_qemu; trap - INT; kill -INT $$' INT
  run_qemu temp.$IMGFORMAT
  ssh_in_to_qemu # -t chroot /mnt bash -l
  shutdown_qemu
  cd $OLDPWD
}

branch_image() {
  qemu-img create -f $IMGFORMAT -b $1 $2
}

write_image() {
  attach_image_to_nbd $1 /dev/nbd0 &&
  sudo dd if=/dev/nbd0 of=$2 bs=4M &&
  detach_image_from_nbd /dev/nbd0
}

convert_image() {
  qemu-img convert -f $IMGFORMAT -O raw $1 $2
}

export_image_for_release() {
  cd work
  qemu-img create -f $IMGFORMAT -b ../$1 temp.$IMGFORMAT
  run_qemu temp.$IMGFORMAT
  # Change mirror in sources.list to our preferred one. This will go horribly 
  # wrong if there is more than one entry in sources.list
  onvm_chroot sh -l -ex <<\EOF
if grep armhf /etc/rpi-issue; then
  sed -i /etc/apt/sources.list -e "s|^deb [^ ]*|deb http://mirrordirector.raspbian.org/raspbian/|"
else
  sed -i /etc/apt/sources.list -e "s|^deb [^ ]*|deb http://http.debian.net/debian|"
fi
cp /usr/share/doc/raspi-config/sample_profile_d.sh /etc/profile.d/raspi-config.sh
apt-get update
# Default to a known-good nameserver
printf "nameserver 8.8.8.8\n" > /etc/resolv.conf
/etc/init.d/fake-hwclock stop # save current time
#rm /usr/bin/raspi-config
#cd /usr/bin
#wget https://raw.github.com/asb/raspi-config/master/raspi-config
#chmod +x raspi-config
cat <<EOF1 > /etc/apt/sources.list.d/raspi.list
deb http://archive.raspberrypi.org/debian/ wheezy main
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
#deb-src http://archive.raspberrypi.org/debian/ wheezy main
EOF1
apt-get update
EOF
  shutdown_qemu
  attach_image_to_nbd temp.$IMGFORMAT /dev/nbd0
  sudo zerofree -v /dev/nbd0p2
  mkdir -p rootfs boot
  sudo mount $BOOT_DEV boot
  sudo mount $ROOT_DEV rootfs
  sudo cp rootfs/etc/rpi-issue boot/issue.txt
  sudo wget http://asbradbury.org/tmp/raspi/oracle_license.txt -O boot/LICENSE.oracle
  [ -n "$RASPBIAN" ] && sudo mv rootfs/etc/ld.so.preload.disable rootfs/etc/ld.so.preload
  if [ -n "$NOOBS" ]; then
    mkdir -p ../noobs
    sudo tar -C boot -cJf ../noobs/boot.tar.xz --preserve .
    sudo tar -C rootfs -cJf ../noobs/root.tar.xz --preserve .
    cat <<\EOF > ../noobs/partition_setup.sh
#!/bin/sh

set -ex

if [ -z "$part1" ] || [ -z "$part2" ]; then
  printf "Error: missing environment variable part1 or part2\n" 1>&2
  exit 1
fi

mkdir -p /tmp/1 /tmp/2

mount "$part1" /tmp/1
mount "$part2" /tmp/2

sed /tmp/1/cmdline.txt -i -e "s|root=/dev/[^ ]*|root=${part2}|"
sed /tmp/2/etc/fstab -i -e "s|^.* / |${part2}  / |"
sed /tmp/2/etc/fstab -i -e "s|^.* /boot |${part1}  /boot |"

umount /tmp/1
umount /tmp/2
EOF
    cat <<\EOF > ../noobs/partitions.json
{
  "partitions": [
    {
      "label": "boot",
      "filesystem_type": "FAT",
      "partition_size_nominal": 60,
      "want_maximised": false,
      "uncompressed_tarball_size": 19
    },
    {
      "label": "root",
      "filesystem_type": "ext4",
      "partition_size_nominal": 2500,
      "want_maximised": true,
      "mkfs_options": "-O ^huge_file",
      "uncompressed_tarball_size": 2280
    }
  ]
}
EOF

    cat <<\EOF > ../noobs/flavours.json
{
  "flavours": [
    {
      "name": "Raspbian - Boot to Scratch",
      "description": "A version of Raspbian that boots straight into Scratch"
    },
    {
      "name": "Raspbian",
      "description": "A Debian wheezy port, optimised for the Raspberry Pi"
    }  
  ]
}
EOF

    cat <<\EOF > ../noobs/os.json
{
  "name": "Raspbian",
  "version": "wheezy",
  "release_date": "2015-02-16",
  "kernel": "3.18",
  "description": "A community-created port of Debian wheezy, optimised for the Raspberry Pi"
}
EOF

    cat <<\EOF > ../noobs/release_notes.txt
2015-02-16:
  * Newer firmware with various fixes
  * New Sonic Pi release
  * Pi2 compatible RPi.GPIO
  * Updated Wolfram Mathematica
2015-01-31:
  * Support for Pi2
  * Newer firmware
  * New Sonic Pi release
  * Updated Scratch
  * New Wolfram Mathematica release
  * Updated Epiphany
2014-12-24:
  * Fix regression with omission of python-pygame
2014-12-22:
  * New firmware with variosu fixes and improvements
  * New UI configuration for lxde
  * Various package updates
  * python3-pygame preinstalled
  * 'nuscratch', scratch running on the Cog StackVM
  * Misc other changes
2014-09-09:
  * New firmware with various fixes and improvements
  * Minecraft Pi pre-installed
  * Sonic Pi upgraded to 2.0
  * Include Epiphany browser work from Collabora
  * Switch to Java 8 from Java 7
  * Updated Mathematica
  * Misc minor configuration changes
2014-06-20:
  * New firmware with various fixes, and kernel bugfix
2014-06-02:
  * Many, many firmware updates with major USB improvements
  * pyserial installed by default
  * picamera installed by default
2014-01-07:
  * Firmware updated
  * Some space saved on the root filesystem
2013-12-20:
  * Firmware updated, includes V4L2 fixes
  * Update omxplayer
2013-12-18:
  * Firmware updated and now using kernel 3.10. Many, many improvements
  * fbturbo XOrg driver is now included and enabled by default. Thanks to 
    ssvb https://github.com/ssvb/xf86-video-fbturbo
  * Update Scratch image with further bug fixes
  * Include Wolfram Mathematica
  * Update to PyPy 2.2
  * Update omxplayer
  * Include v4l-utils for use with experimental V4L2 Raspberry Pi camera driver
  * Update squeak-vm to fix issues with loading JPEGs
2013-09-25:
  * Update Scratch image for further performance improvements
  * Include Oracle JDK
  * At least a 4GiB SD card is now required (see above)
  * Include PyPy 2.1
  * Include base piface packages
  * Update raspi-config to include bugfix for inheriting language settings 
    from NOOBS
2013-09-10:
  * Updated to current top of tree firmware
  * Update squeak-vm, including fastblit optimised for the Raspbery Pi
  * Include Sonic Pi and a fixed jackd2 package
  * Support boot to Scratch
  * Inherit keyboard and language settings from NOOBS
EOF
    wget http://asbradbury.org/tmp/raspi/marketing.tar -O ../noobs/marketing.tar
  
  fi
  sleep 10
  sudo umount $BOOT_DEV
  sudo umount $ROOT_DEV
  detach_image_from_nbd /dev/nbd0
  convert_image temp.$IMGFORMAT ../$2
}

disable_starting_services() {
  ssh_in_to_qemu chroot /mnt sh -ex - <<EOF
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d
chmod 755 /usr/sbin/policy-rc.d
EOF
}

allow_starting_services() {
  ssh_in_to_qemu chroot /mnt sh -ex - <<EOF
rm /usr/sbin/policy-rc.d
EOF
}

save_space_using_hardlink() {
  onvm_chroot sh -l -e - <<\EOF
hardlink -t /usr/share/doc
EOF
}

mount_apt_cache() {
  ssh_in_to_qemu sh -e - <<EOF
  mount -o noatime /dev/sdc /mnt/var/cache/apt
EOF
}

download_if_necessary() {
  [ -f $(basename "$1") ] || wget "$1"
}

get_git_hash() {
  git log --pretty=format:'%h' -n 1
}

update_issue() {
  GIT_HASH=$(get_git_hash) &&
  CUR_DATE=$(date "+%Y-%m-%d")
  onvm_chroot sh -l -e <<EOF
printf "Raspberry Pi reference $CUR_DATE ($TGT_ARCH)\nGenerated using spindle, http://asbradbury.org/projects/spindle/, $GIT_HASH, $CURIMG\n" > /etc/rpi-issue
EOF
}

# Create a list of all installed packages, all manually installed packages and 
# the debconf selections
fingerprint_debian() {
  FNGPRNT_DIR="$CURIMG.fingerprint" &&
  onvm_chroot sh -l -e <<EOF &&
mkdir -p "/tmp/$FNGPRNT_DIR"
cd "/tmp/$FNGPRNT_DIR"
dpkg --get-selections > dpkg_selections
dpkg -l > dpkg_l
apt-mark showauto > apt-mark_showauto
debconf-get-selections > debconf_selections
EOF
  scp_r_from_qemu "/mnt/tmp/$FNGPRNT_DIR" . &&
  cd "$FNGPRNT_DIR" &&
  grep "[[:space:]]install$" dpkg_selections | cut -f1 | sort apt-mark_showauto - | uniq -u > manually_installed_packages &&
  cd "$OLDPWD"
}

finish_image() {
  chmod -w $CURIMG &&
  mkdir -p ../$OUTDIR &&
  mv -f $CURIMG ../$OUTDIR &&
  if [ -d "$CURIMG.fingerprint" ]; then
    rm -rf "../$OUTDIR/$CURIMG.fingerprint" &&
    mv "$CURIMG.fingerprint" ../$OUTDIR
  fi &&
  FINISHED_SUCCESSFULLY=1 &&
  printf "Completed script successfully\n"
}

# Usage: ask_yn default prompt [printf_args...]
# Read a Yes / No user response. If $ASK_YN_USE_DEFAULT is set, assume the 
# default. Return of 0 indicates a yes, and non-zero is no.
ask_yn() {
  OPT="y/n"
  case "$1" in
    y*|Y*) OPT="Y/n"; RET=0;;
    n*|N*) OPT="y/N"; RET=1;;
    *) die "Programmer error: invalid argument to ask_yn"
  esac
  shift
  if [ "$ASK_YN_USE_DEFAULT" ]; then
    return $RET
  else
    PROMPT=$1
    shift
    printf "$PROMPT [$OPT]: " "$@" && read YN
    case "$YN" in
      y*|Y*) return 0;;
      n*|N*) return 1;;
      "") return $RET
    esac
  fi
}

# Usage: read_val var default prompt [printf_args...]
read_val() {
  RV_VAR=$1
  RV_DEFAULT=$2
  RV_PROMPT=$3
  shift 3
  printf "$RV_PROMPT [$RV_DEFAULT]: " "$@" && read RV_VAL
  [ -z "$RV_VAL" ] && RV_VAL=$RV_DEFAULT
  eval "$RV_VAR=\$RV_VAL"
}

die() {
  FMTSTR=$1
  shift
  printf "Died: $FMTSTR\n" "$@" >&2
  exit 1
}

# do a task
# Inspects $DOTASK_MODE to see if the user:
#   e*: wants to have the command echoed
#   q*: wants to be questioned as to whether to run, skip or quit.
# If the task fails it asks the user whether to quit (default: y), or if 
# $DOTASK_QUIT_ON_FAILURE is set then quit without asking the user.
dotask() {
  case "$DOTASK_MODE" in
    e*) printf "Run: '%s'\n" "$*"; false;;
    q*) printf "Run: '%s' [Y/n/q] ? " "$*" && read SHOULD_RUN;
        case "$SHOULD_RUN" in
          n*|N*|s*|S*)  printf "Skip '%s'\n" "$*";;
          q*|Q*)        die "Exit at user request";;
          *)            false;;
        esac;;
    *)  false;;
  esac || "$@" || { RC=$?
    [ -z "$DOTASK_QUIT_ON_FAILURE" ] && printf "'%s' failed (returned $RC) - Quit? [Y/n] : " "$*" && read YN
    case "$YN" in n*|N*) true ;; *) die "Failed while performing task: %s" "$*";; esac;
  }
}


FINISHED_SUCCESSFULLY=0
CLEANED_UP=0
WAS_TRAPPED=0

# Sigh http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=390433
trap 'WAS_TRAPPED=1; universal_cleanup' EXIT
trap 'WAS_TRAPPED=1; universal_cleanup; trap - INT; kill -INT $$' INT

universal_cleanup() {
  set +e
  if [ $CLEANED_UP -ne 1 ]; then
    printf "Initiating cleanup\n"
    trap - INT
    trap - EXIT
    [ -p fifo.in ] && shutdown_qemu
    [ -b "$BOOT_DEV" ] && sudo umount $BOOT_DEV
    [ -b "$ROOT_DEV" ] && sudo umount $ROOT_DEV
    [ -b "$NBD_DEV" ] && sudo umount $NBD_DEV
    [ -b "$NBD_DEV" ] && detach_image_from_nbd $NBD_DEV
  fi
  if [ $WAS_TRAPPED -eq 1 ] && [ $FINISHED_SUCCESSFULLY -eq 0 ]; then
    printf "Did not complete script successfully\n"
  fi
  set -e
}
