#!/bin/sh

###############################################################################
# BASH script to manage the Mohj-O-LOader booter.
#
# Company: Adtec Digital, Inc.
# Author:  Andre G Ancelin
# Date:    February 10, 2004
# Revisions:
#          Initial version, 0.1 on 2004-02-10. AGA
#
# The following tree structure is utilized:
# /
# + boot                            Boot directory, root accesible ONLY!
#     + molo                        This script!
#     + [zImage.elf.bin-CCYY-MM-DD] Kernel binary images
#     + [primary|secondary|safe]    Partition directories
#         + comments                MOLO comments field text file
#         + device                  Device file, usually a symlink to ../../dev/hdxx
#         + dump                    Dumped binary file, only present if -d option used
#         + kernel                  Symlink to desired kernel file in ../
#         + parameters              MOLO parameters field text file- 1 line only!
#
# The structure of the MOLO header is:
# typedef struct {
#	T32 :32;                        /* Block 0,Reserved (0 by default) */
#	T32 api;                        /* Api: 'API\0' string. This MUST be here!!! */
#	T32 major;                      /* Major: Major version of bootloader, currently 1 */
#	T32 minor;                      /* Minor: Minor version of bootloader, currently 0 */
#	T8  rsv_a[16];                  /* Reserved (0 by default) */
#	T8  loader[32];                 /* Loader: currently 'Mohj-O-Loader (MOLO)' */
#   T8  os[32];                     /* Operating System: currently 'GNU/Linux' */
#	T8  image[32];                  /* Image: currently 'vmlinux,binary' */
#	T8  rsv_b[256-128];             /* Reserved (0 by default) */
#	T8  comments[512-256];          /* Comments: Anything at all */
#   T8  parameters[2048-512];       /* Blocks 1-3, Boot parameter string */
#	} TBootImage;                   /* Blocks 4-n, Binary OS image */
#
###############################################################################





#==============================================================================
# Global Variables

TALK=""
ECHO="echo -e"
VER=0.1
DATE=2004-02-10





#==============================================================================
# Functions


#------------------------------------------------------------------------------
# Talk function.
# Used to chatter feedback as command executes.
Talk ()
{
  if [ "$TALK" = "YES" ] ; then
    $ECHO "MOLO: $1"
  fi
}

#------------------------------------------------------------------------------
# Display usage of script.

Usage ()
{
  local PROG=`basename $0`

  $ECHO ""
  $ECHO "\"$PROG\" is the Mohj-O-LOad booter for Micromon & GNU/Linux based systems."
  $ECHO "The Mohj-O-LOad boot manager uses raw partitions to hold both a header"
  $ECHO "and a binary image without any filesystem. It is very simple!"
  $ECHO ""
  $ECHO "Usage: ./$PROG [options] [partition], where [options] are:"
  $ECHO "       -a             = Auto bootable (only used with -c option)"
  $ECHO "       -b [partition] = Make partition auto bootable"
  $ECHO "       -c [partition] = Create a boot partition"
  $ECHO "       -d [partition] = Dump a boot partition image to \"[partition]/dump\" file"
  $ECHO "       -h             = Help (this screen)"
  $ECHO "       -t             = Talk, prints lots of debug feedback"
  $ECHO "       -v             = Version"
  $ECHO "Examples:"
  $ECHO "  \"./$PROG -ac [partition]\" creates a boot partition and make it auto bootable."
  $ECHO "  \"./$PROG -b [partition]\" makes an existing boot partition auto bootable."
  $ECHO "  \"./$PROG -c [partition]\" creates a boot partition."
  $ECHO "  \"./$PROG -d [partition]\" dumps a boot partition image to \"[partition]/dump\"."
  $ECHO ""
  $ECHO "The [partition] argument is a directory name in \"/boot\"."
  $ECHO "This directory requires 4 files:"
  $ECHO "    \"comments\"  - A text file of comments for the boot loader."
  $ECHO "    \"device\"    - A symbolic link to the partition utilized."
  $ECHO "                  Examples: \"/dev/hde1\" in \"primary\""
  $ECHO "                            \"/dev/hde2\" in \"secondary\""
  $ECHO "                            \"/dev/hde3\" in \"safe\""
  $ECHO "    \"kernel\"    - A symbolic link to the desired kernel binary image."
  $ECHO "                  Kernel binaries are stripped elf files converted to binary"
  $ECHO "                  format using the \"objcopy\" tool and should be of the name"
  $ECHO "                  form \"zImage.elf.bin-CCYY-MM-DD\". They should also reside"
  $ECHO "                  in the \"/boot\" directory."
  $ECHO "    \"parameters\"- A text file of 1 line (no \\\n) of kernel boot parameters."
  $ECHO "In addition, the \"dump\" file may be present if a dump (-d) command was issued."
  $ECHO ""
}

#------------------------------------------------------------------------------
# Display version.

Version ()
{
  $ECHO "molo, the Mohj-O-LOad boot manager."
  $ECHO "Version $VER, $DATE"
  $ECHO "Copyright (c) 2004 by Adtec Digital, Inc."
  $ECHO "Written by Andre Ancelin"
}

#------------------------------------------------------------------------------
# Make sure partitin exists.

PartitionExists ()
{
  local DIR=$1                  # Partition (named directory)

  #-- Make sure named directory exists as such --
  if [ ! -d "$DIR" ] ; then
    $ECHO "ERROR- \"$DIR\" directory does not exist."
    exit 1
  fi
}

#------------------------------------------------------------------------------
# Move block(s) from an input file -OR- input string to an output file.

MoveBlocks ()
{
  local IN=$1               # Input file -OR- Input string
  local SIZE=$2             # Size of a block, in bytes
  local OFFSET=$3           # Offset, in blocks
  local COUNT=$4            # Count, in blocks
  local OUT=$5              # Output file

  #-- Determine size, in bytes, of total transfer & create common dd options --
  let "BYTES = $COUNT * $SIZE"
  local OPTS="of=$OUT ibs=$BYTES cbs=$BYTES obs=$SIZE seek=$OFFSET conv=sync"

  #-- If $IN is a file, then use 'if' option in dd command. --
  #-- If not, pipe $IN string (via stdin) to dd command.    --
  if [ -n "$IN" -a -e "$IN" ] ; then
    dd if=$IN $OPTS 2> /dev/null
    Talk "(MoveBlocks) dd if=$IN $OPTS"
  else
    echo -en "$IN" | dd $OPTS 2> /dev/null
    Talk "(MoveBlocks) echo -en \"$IN\" | dd $OPTS"
  fi
}

#------------------------------------------------------------------------------
# Create a boot partition.
#
# NOTE- If you wish to directly write to a hard drive partition,
# create a symbolic link named 'device' in the named partition directory and
# point it to the desired device (i.e. /dev/hda1). Otherwise, simply
# delete the 'device' to direct the output to a regular file named 'device'.

CreatePartition ()
{
  local DIR=$1                       # Partition (named directory)
  local AUTO=$2                      # Auto-bootable option, TRUE if not empty
  local OUT="$DIR/device"            # Output file
  local PART=$(find $OUT -printf "%l\n")  # dereference the partition 
  local DEV=${PART//[[:digit:]]}     # strip the digit to get the device
  local COMMENTS="$DIR/comments"     # Comments text file
  local PARAMETERS="$DIR/parameters" # Parameters text file
  local KERNEL="$DIR/kernel"         # Kernel file

  #-- Make sure user supplied input files exists --
  $ECHO "Creating a boot partition using the \"$DIR\" directory ..."
  if [ ! -e "$COMMENTS" ] ; then
    $ECHO "ERROR- \"$COMMENTS\" file does not exist."
    exit 1;
  fi
  if [ ! -e "$PARAMETERS" ] ; then
    $ECHO "ERROR- \"$PARAMETERS\" file does not exist."
    exit 1;
  fi
  if [ ! -e "$KERNEL" ] ; then
    $ECHO "ERROR- \"$KERNEL\" file does not exist."
    exit 1;
  fi

  #-- Write the fixed format MOLO required header info --
  MoveBlocks "\0" 1 0 4 $OUT
  MoveBlocks "API" 1 4 4 $OUT
  MoveBlocks "\0\0\0\1" 1 8 4 $OUT
  MoveBlocks "\0\0\0\0" 1 12 4 $OUT
  MoveBlocks "\0" 1 16 16 $OUT
  MoveBlocks "Mohj-O-LOad" 1 32 32 $OUT
  MoveBlocks "GNU/Linux" 1 64 32 $OUT
  MoveBlocks "vmlinux,binary" 1 96 32 $OUT
  MoveBlocks "\0" 1 128 128 $OUT

  #-- Write the user defined MOLO header info & kernel --
  COMMENTS=`cat $COMMENTS`
  COMMENTS="$COMMENTS\n(MOLO created on `date \"+%A, %B %d, %Y\"` at `date \"+%H:%M:%S\"`)"
  MoveBlocks "$COMMENTS" 256 1 1 $OUT
  MoveBlocks "$PARAMETERS" 512 1 3 $OUT
  MoveBlocks "$KERNEL" 512 4 4092 $OUT
  if [ "$SKIP_FLUSH" != "TRUE" ]; then 
    $ECHO "Flushing $DEV device buffer, please wait."
    blockdev --flushbufs $DEV
  sleep 10
  fi
  $ECHO "... completed OK"

  #-- If this partition is to be auto-bootable, make as such --
  if [ "$AUTO" = "YES" ] ; then
    Bootable $DIR
  fi
}

#------------------------------------------------------------------------------
# Dump a boot partition.
# The dump will go to the "dump" file in the named partition directory.

DumpPartition ()
{
  local DIR=$1                  # Partition (named directory)
  local IN="$DIR/device"        # Input file
  local OUT="$DIR/dump"         # Output file

  #-- Make sure input files exists --
  $ECHO "Dumping a boot partition to the \"$OUT\" file ..."
  if [ ! -e "$IN" ] ; then
    $ECHO "ERROR- \"$IN\" file does not exist."
    exit 1;
  fi

  #-- Transfer the entire partition to the "dump" image --
  cp $IN $OUT
  sync
  $ECHO "... completed OK"
}

#------------------------------------------------------------------------------
# Make a partition bootable.
#

Bootable ()
{
  local DIR=$1                  # Partition (named directory)
  local OUT=$DIR/device         # Output file, MUST be a symlink to a device file

  #-- Make sure output device file exists --
  $ECHO "Making the partition auto bootable using the \"$DIR\" directory ..."
  if [ ! -e "$OUT" ] ; then
    $ECHO "ERROR- \"$OUT\" does not exist for the partition"
    exit 1
  fi

  #-- Make sure device file is, in fact, a device (even if symbolic link)   --
  #-- This is done by getting a long ls listing first, then extracting the  --
  #-- 'dev/*' component from the listing. Finally, the partition number is  --
  #-- extracted from the device name and verified to be a primary partition --
  local LS=`ls -l "$OUT"`
  Talk "(Bootable) \$LS= $LS"
  local DRIVE=`expr "$LS" : '.*[ ]\([ ./]*dev/[^0-9]*\)'`
  Talk "(Bootable) \$DRIVE= $DRIVE"
  local DEVICE=`expr "$LS" : '.*[ ]\([ ./]*dev/[^ ]*\)'`
  Talk "(Bootable) \$DEVICE= $DEVICE"
  if [ -z "$DEVICE" ] ; then
    $ECHO "ERROR- \"$OUT\" is not a device or a symbolic link to a device"
    exit 1
  fi
  local DEV_NUM=`expr "$DEVICE" : '[^0-9]*\([0-9]*\)'`
  Talk "(Bootable) \$DEV_NUM= $DEV_NUM"
  if [ -z "$DEV_NUM" ] ; then
    $ECHO "ERROR- \"$DEVICE\" is not a physical partition"
    exit 1
  fi
  let "NUM = DEV_NUM"
  if [ $NUM -lt 1 -o $NUM -gt 4 ] ; then
    $ECHO "ERROR- \"$DEV_NUM\" is not a primary partition number"
    exit 1
  fi

  #-- Iterate through the primary partition table, --
  #-- clearing all non-bootable partitions and     --
  #-- setting the single bootable partition        --
  let "NUM = (NUM * 16) + 430"
  for ((I=446; I<510; I+=16)) ; do
    if [ $I -eq $NUM ] ; then
      echo -en "\x80" | dd seek=$I of=$DRIVE bs=1 count=1 2> /dev/null
    else
      echo -en "\x00" | dd seek=$I of=$DRIVE bs=1 count=1 2> /dev/null
    fi
  done
  $ECHO "... completed OK"
}





#==============================================================================
# Start script execution here

#--------------------------------------
# Global flags setup by parser.

AUTOBOOT=""
OPERATION=""
PARTITION=""
HOSTIS=""
FORCE=""
SKIP_FLUSH=""

#--------------------------------------
# Parse up all arguments

if [ "$#" -eq 0 ] ; then
  Usage
  exit 0
fi
while getopts "ab:c:d:htvfs" OPTION ; do
  case $OPTION in
    a)
      AUTOBOOT="YES"
      ;;
    b)
      OPERATION="BOOTABLE"
      PARTITION=$OPTARG
      ;;
    c)
      OPERATION="CREATE"
      PARTITION=$OPTARG
      ;;
    d)
      OPERATION="DUMP"
      PARTITION=$OPTARG
      ;;
    h)
      OPERATION="HELP"
      ;;
    t)
      TALK="YES"
      ;;
    v)
      OPERATION="VERSION"
      ;;
    f)
      FORCE="TRUE"
      ;;
    s)
      SKIP_FLUSH="TRUE"
      ;;
    ?)
      $ECHO "ERROR- Illegal command line option"
      exit 1
      ;;
  esac
done

if [ "${FORCE}" != "TRUE" ]; then
  HOSTIS=`printenv HOSTTYPE`
  if [ "$HOSTIS" = "i386" ] ; then
    $ECHO "WARNING- This appears to be an i386 PC"
    $ECHO "         This program WILL try to manipulate drive partitions"
    $ECHO "         If you wish to continue, type \"go<ENTER>\""
    read GO
    if [ "$GO" != "go" ] ; then
      exit 1
    fi
  fi
fi
#--------------------------------------
# Command line has been parsed,
# execute argued operation.

case $OPERATION in
  BOOTABLE)
    PartitionExists $PARTITION
    Bootable $PARTITION
    ;;
  CREATE)
    PartitionExists $PARTITION
    CreatePartition $PARTITION $AUTOBOOT
    ;;
  DUMP)
    PartitionExists $PARTITION
    DumpPartition $PARTITION
    ;;
  HELP)
    Usage
    ;;
  VERSION)
    Version
    ;;
esac
