#!/bin/ash

# This file is part of PLD batch-installer
# Copyright (C) 2001 CYBER Service
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# Author: Rafa Kleger-Rudomin
# Contributors: Micha Moskal


# this script is responsible for preparing destination filesystem
#
# things to do:
# 1. check if creating partitions is possible
# 2. delete existing partitions, create new ones if necessary
# 3. create filesystems where necessary
# 4. mount whole dest filesystem
# 5. create dest/etc/fstab

# WARNING: unless dry_run=no is set, only simulate

set -e 
#set -x

PATH=$PATH:. . installer-functions

find_config_paths $@
load_config

################# utility functions ####################


# $1 - device name
# list partitions for give device:
# part_no start end type filesystem flags
list_partitions () {
    parted -s "${1:?Error: no device name given}" print | grep '^[0-9]\+ ' | sed "s@^@${1} @"
}


# $1 - device name
make_label_if_not_exist () {
    if parted -s "${1:?Error: no device name given}" print | \
       grep -q 'Error:.*unrecogni.ed disk label' ; then
	log verb "Creating new empty msdos label."
	if [ "X$dry_run" = "Xno" ] ; then
	    log_wrap parted -s "$1" mklabel msdos
	fi
    fi
    
    if parted -s "${1:?Error: no device name given}" print | grep -q "^Disk label type:"; then
	:
    else
	die "Creating new msdos label failed!"
	log_wrap_verb parted -s "$1" print
    fi
}

# $1 - device name
# shows disk parameters:
# start end label_type
disk_info () {
    local field1 rest firstline start end label_type
    firstline=1
    
    parted -s "${1:?Error: no device name given}" print \
    | grep '^Disk ' | sed 's/^Disk [^:]*: //' \
    | while read field1 rest; do
	if test "$firstline"; then 
	    start=`echo $field1 | sed 's/-.*//' | sed 's/\.//'`
	    end=`echo $field1   | sed 's/.*-//' | sed 's/\.//'`
	    echo -n "$1 $start $end "
	else
	    label_type=$field1
	    echo "$label_type"
	    break
	fi
	firstline=""
    done
}


################# some preparation ####################

all_partitions=""
id=1
soft_raid=no
hard_raid=no
while true
do
  eval "part=\$dest_part${id}_device"
  if [ -z "$part" ] ; then
    break
  else
    if echo $part | grep -q raid ; then
      : omit raid entries
      soft_raid=yes
    else
      if echo $part | grep -q '/c[0-9]d[0-9]' ; then
        hard_raid=yes
      fi
      all_partitions="${all_partitions}${all_partitions:+ }${part}"
    fi
  fi
  id=`expr $id + 1`
done
log debug "all_partitions = $all_partitions"
unset part

# CHECK PHASE
# 1. find existing partitions  -> /tmp/existing_partitions.${dev}
# 2. find partitions to create -> /tmp/create_partitions.${dev}
# 3. check if there is enough space, etc

log warn "`nls "Checking existing partitions"`"

# make sure no stale files exists
rm -f /tmp/existing_partitions
rm -f /tmp/create_partitions

echo -n >/tmp/existing_partitions
echo -n >/tmp/disk_info
echo -n >/tmp/create_partitions

# operate on copy of list
dev_actions="${dest_devices_actions:?Error: dest_devices_actions not set}"
# iterate through devices
for dev in ${dest_devices:?Error: dest_devices not set}; do
    # shift the lists for next device (disk)
    action=`list_car $dev_actions`
    dev_actions=`list_cdr $dev_actions`
    
    log debug "device: $dev, partitions_action: $action"

    case "$action" in
    "make_new")
        make_label_if_not_exist ${dev}
	;;
    "use_existing")
       if parted -s "${dev}" print | grep -q "^Disk label type:"; then
         : ok
       else
         die "parted has problems with your disk"
       fi
       ;;
    esac

    # let's look around
    list_partitions ${dev} >>/tmp/existing_partitions
    disk_info ${dev} >>/tmp/disk_info
    test -f /tmp/existing_partitions
    test -f /tmp/disk_info

    # disk_info creates label if there is none
    label=`grep "^${dev} " /tmp/disk_info | cut -d ' ' -f 4 || :`
    : ${label:?Error: label not set}

    # let's count existing parts
    part_counter=0
    have_extended=""

    
    # some preparation first
    case "$action" in
    "make_new")
	# nothing to do ?
	:
	;;
    "use_existing")
	# check if all required partitions already exist 
        log debug "checking part existence ($all_partitions)"
	for part in $all_partitions; do
	  if echo "$part" | grep -q "^${dev}p\?[0-9][0-9]*$"; then
	    number=`echo $part | sed -e "s@^${dev}@@; s/^p//"`
    	    grep -q "^${dev} ${number} " "/tmp/existing_partitions" || \
	      die "partitions: use_existing specified and no partition ${part}, number $number, exists"
	  fi
	done
	unset part
	unset number
	;;
    *)
	die "bad action for $dev"
	;;
    esac

    # now available:
    #  part_counter

    log debug "what to create?"
    
    echo -n >/tmp/create_partitions.tmp
    # find partitions to be created on given device
    id=0
    while :
    do
	id=`expr $id + 1 || :`
	log debug "Processing dest_part$id"
	eval_partition_info $id
	log debug "dest_part${id}_device=$dest_part_device"
	test "$dest_part_device" || break

	# check if given partition is to be created on current $dev
	echo "$dest_part_device" | grep -q "^${dev}" || continue

	case "$action" in 
	"make_new")
	    # here only the disk devices may be specified
	    # numbers are assigned automatically
	    part_counter=`expr $part_counter + 1 || :`
	    dest_part_size=`echo $dest_part_size | sed -e 's/ *of *//'`
	    dest_part_number=$part_counter
	    if test "$part_counter" -lt 4 -o "$label" != "msdos" ; then
		# create primary
		dest_part_type=primary
	    elif test "$part_counter" -eq 4; then
		# TODO hmm we should forsee if there is only 4 parts to create, then then we could avoid 'extended'
		# create extended 
		dest_part_type=extended
		# this partition entry should be processed once again to add logical
		id=`expr $id - 1 || :`
	    else
		# create logical - this is Microsoft logic only, though
		dest_part_type=logical
	    fi
	    ;;
	"use_existing")
	    # here the create_partitions also must be generated
	    # they will not be really created, but may be formatted and mounted

	    dest_part_number=`echo $dest_part_device | sed -e "s@^${dev}@@; s/^p//"`

	    # if number is really a number, this partition will be on current $dev
	    if echo "$dest_part_number" | grep -q '^[0-9][0-9]*$'; then
		log debug "  $dest_part_mnt_point will be on $dev, OK!!!"
	    else
		die "Fatal: invalid partition device"
	    fi
	    dest_part_size=not_used
	    dest_part_type=not_used
	    ;;
	*) die "unknown action $action for disk $dev" ;;
	esac

	log debug "  $dest_part_mnt_point: $dest_part_device $dest_part_size $dest_part_format_partition"
	# create entry with partition data
	echo \
		    "${dest_part_number:?Error: dest_part_number not set}" \
		    "${dest_part_size:?Error: dest_part_size not set}" \
		    "${dest_part_type:?Error: dest_part_type not set}" \
		    "${dest_part_filesystem:?Error: dest_part_filesystem not set}" \
		    "${dest_part_format_partition:?Error: dest_part_format_partition not set}" \
		    "${dest_part_mnt_point:?Error: dest_part_mnt_point not set}" \
		    "${dest_part_options:-defaults}" \
		    "${dest_part_format_options}" \
		      >>/tmp/create_partitions.tmp

    done
    sort -n /tmp/create_partitions.tmp | sed "s@^@${dev} @" >>/tmp/create_partitions
    rm -f /tmp/create_partitions.tmp
done


# three files available now:
# 1) /tmp/existing_partitions:
# device part_no start end    type filesystem flags
# e.g
# /dev/hda 1     0.000 99.999 primary ext2     boot

# 2) /tmp/create/partitions:
# device part_no size type filesystem format_partition mnt_point options format_options
# e.g 
# /dev/hda  1    300 primary ext2           yes          /home   gid=500    hmm...

# 3) /tmp/disk_info
# device   begin   end   labe_type
# e.g.
# /dev/hda 0000 4100857 msdos


if [ $soft_raid = yes -o $hard_raid = yes ] ; then
  . installer-raid-functions
fi

if [ $soft_raid = yes ] ; then
  prep_soft_raid
fi

# at this point /tmp/raidtab file has been created, see raidtab(5) for
# its syntax. also new partitions has been added to /tmp/create_partitions.


# COUNT PHASE:
# 1. Evaluate percents to real amounts of space to be allocated for every partition
# 2. Check if there is enough space

echo -n >/tmp/create_partitions.parted
dev_actions="${dest_devices_actions:?Error: dest_devices_actions not set}"

for dev in ${dest_devices:?Error: dest_devices not set}; do
    action=`list_car $dev_actions`
    dev_actions=`list_cdr $dev_actions`

    # if 'use_existing' set for $dev, skip
    test "$action" = "make_new" || continue

    grep "^${dev} " /tmp/disk_info \
	| while read devjunk disk_begin disk_end rest; do
	save_var disk_begin
	save_var disk_end
	done
    load_var disk_begin
    load_var disk_end
	

    # total disk capacity
    total=`expr $disk_end - $disk_begin || :`
    first_free=0
    test "$total" -gt 0 || die "Fatal: no total set!"

    # find how much space was explictly requested, or expressed as % of all 
    statically_allocated=0
    save_var statically_allocated
    grep "^${dev} " /tmp/create_partitions > /tmp/create_partitions.dev || :
    while read device minor size_mb part_type rest; do
	if test "$part_type" != "extended"; then
	    if echo "$size_mb" | grep -q '^[0-9]\+$'; then
		size=`mega_to_kilo $size_mb || :`
	    elif echo "$size_mb" | grep -q '%a'; then
		percent=`echo $size_mb | sed 's/%a.*//'`
		# total must be divided by 100 first because of 'expr' overflow
		size=`expr $total / 100 \* $percent || :`
	    else
		continue
	    fi
	    test "$size" -lt "$total" || die "requested partition $minor greater than disk $dev!"
	    test `expr $total - $statically_allocated` -gt $size || die "no space to allocate partition $minor on disk $dev!"
	    statically_allocated=`expr $statically_allocated + $size || :`
	fi
	save_var statically_allocated
    done < /tmp/create_partitions.dev
    load_var statically_allocated

    dynamically_allocated=`expr $total - $statically_allocated || :`

    # now we can produce final table of partitions for parted and mount

    begin=$first_free
    echo -n >/tmp/create_partitions.tmp
    while read device minor size_mb part_type rest; do
	if test "$part_type" != "extended"; then 
	    if echo "$size_mb" | grep -q '^[0-9]\+$'; then
		size=`mega_to_kilo $size_mb`
	    elif echo "$size_mb" | grep -q '%a'; then
		percent=`echo $size_mb | sed 's/%a.*//'`
		# total must be divided by 100 first because of 'expr' overflow
		size=`expr $total / 100 \* $percent || :`
	    elif echo "$size_mb" | grep -q '%f'; then
		percent=`echo $size_mb | sed 's/%f.*//'`
		size=`expr $dynamically_allocated / 100 \* $percent || :`
	    else
		die "Fatal: size_mb in unhandled format"
	    fi
	    test $size -ge 2000 || die "hmm, computed partition $minor size < 2MB, giving up"
	    end=`expr $begin + $size - 1 || :`
	    echo \
		    "${device:?Error: device not set}" \
		    "${minor:?Error: minor not set}" \
		    "${begin:?Error: begin not set}" \
		    "${end:?Error: end not set}" \
		    "${part_type:?Error: part_type not set}" \
		    "${rest:?Error: rest not set}" \
		      >>/tmp/create_partitions.tmp
	    begin=`expr $begin + $size || :`
	else
	    # a placeholder for extended
	    echo 'extended' >>/tmp/create_partitions.tmp
	    extended_begin=$begin
	    extended_minor=$minor
	    save_var extended_begin
	    save_var extended_minor
	fi
	extended_end=$end
	save_var extended_end
    done < /tmp/create_partitions.dev

    if grep -q '^extended' /tmp/create_partitions.tmp; then
	load_var extended_minor
	load_var extended_begin
	load_var extended_end
	# now insert extended partion
	sed "s#^extended#$dev $extended_minor $extended_begin $extended_end extended#" \
	    /tmp/create_partitions.tmp >>/tmp/create_partitions.parted
    else
	cat /tmp/create_partitions.tmp >>/tmp/create_partitions.parted
    fi

    rm -f /tmp/create_partitions.tmp

done

# now the additional file is available:
# 2) /tmp/create/partitions.parted:
# device part_no begin_kb end_kb type filesystem format_partition mnt_point options format_options
# e.g 
# /dev/hda  1    0        300000 primary ext2           yes          /home   gid=500    hmm...


# now things are getting serious
# RUN PHASE:
# 1. delete partitions (if necessary)
# 2. create new partitions (if necessary)

log warn "`nls "Deleting and creating partitions (if necessary)"`"

# first unmount all the destination if they were already mounted
# useful for developers that run this script multiple times
# hmm, busybox does not have swapon -s
if test "x$dry_run" = "xno"; then
    if test -L /var/log/installer ; then
      if test -r ; then
        tail -n 500 /var/log/installer > /var/log/installer.tmp
      else
        echo -n > /var/log/installer.tmp
      fi
      rm -f /var/log/installer
      mv /var/log/installer.tmp /var/log/installer
    fi
    
    cat /proc/swaps | grep '^/' \
	| while read dev rest; do 
	    log debug "Turning off $dev"
	    swapoff $dev || : 
	  done
    # umount /dir/subdir before /dir
      cat /proc/mounts | grep " /dest" | sed 's/[^ ]* //' | sort -r \
	| while read dir rest; do 
	    log debug "Trying unmount $dir"
	    umount $dir
	  done

    if test -f /proc/mdstat ; then 
      grep '^md[0-9]' /proc/mdstat | sed -e 's/md\([0-9]*\).*/\1/' | sort -r -n | \
      while read dev ; do
        log debug "Stopping RAID device $dev"
	if [ $mdctl = yes ] ; then
	  mdctl --stop /dev/md$dev 2> /dev/null || :
	else
	  raidstop /dev/md$dev 2> /dev/null || :
	fi
      done
    fi
fi

# iterate through devices
# operate on copy of list
dev_actions="${dest_devices_actions:?Error: dest_devices_actions not set}"

for dev in ${dest_devices:?Error: dest_devices not set}; do
    action=`list_car $dev_actions`
    dev_actions=`list_cdr $dev_actions`
    
    # deleting
    case "$action" in
    make_new)
	log info "`nls "Deleting existing partitions on disk %s" "$dev"`"
	if test "x$dry_run" = "xno"; then
           grep "^${dev} " /tmp/existing_partitions | sed "s@^${dev} @@" | sort -r -n \
	   | while read minor rest; do
		log debug "Deleting partition $minor on $dev"
		log_wrap parted -s "${dev:?}" rm ${minor:?}
	   done
	fi
	;;
    *)
	# nothing to do
	;;
    esac

    # creating
    case "$action" in
    "make_new")

	log info "`nls "Creating new partitions on disk %s" "$dev"`"

	grep "^${dev} " /tmp/create_partitions.parted | \
	while read device minor begin end part_type filesystem rest; do
	    case $filesystem in
	    swap ) 
	      filesystem=linux-swap
	      ;;
	    md )
	      filesystem=ext2
	      ;;
	    ext2 | reiserfs | ext3 | jfs | xfs)
	      # nothing to do...
	      ;;
	    "" )
	      # hmmm
	      ;;
	    * )
	      die "bad filesystem: $filesystem."
	      ;;
	    esac
	    begin_mb=`kilo_to_mega $begin || :`
	    end_mb=`kilo_to_mega $end || :`
	    # in case of extended there is no filesystem to set
	    log info "$(nls "  Creating %s partition %s" "${part_type:?}" "$(part ${dev} ${minor})" )"
            if test "x$dry_run" = "xno"; then
		log_wrap parted -s ${dev:?} mkpart ${part_type:?} $filesystem ${begin_mb:?} ${end_mb:?}
		if test "X$filesystem" = "Xmd" ; then
		  log_wrap parted -s ${dev} set ${minor} raid on 
		fi
            fi
	done
	;;
    *)
	# nothing to do
	:
	;;
    esac
done


# 2a. create/start raid devices
if [ $soft_raid = yes ] ; then
  start_soft_raid
fi

# 3. create filesystems

log warn "`nls "Creating filesystems"`"

for dev in ${dest_devices:?Error: dest_devices not set} /dev/md; do
    devbase=`basename ${dev} || :`
    # format
    log info "`nls "Making filesystems on %s" "$dev"`"
    grep "^${dev} " /tmp/create_partitions > /tmp/create_partitions.dev || :
    while read device minor size part_type filesystem format dir options filesystem_options; do
	if test "x$format" = "xyes" -a "x$part_type" != "xextended"; then
	    full_dev=`part ${dev} ${minor}`
	    # info to always display this
	    log info "`nls "Making filesystem on partition %s (type %s) [it might take a while...]" "$full_dev" "$filesystem"`"
	    case "$filesystem" in
	    swap )
		if test "x$dry_run" = "xno"; then
		    log_wrap mkswap $filesystem_options "$full_dev" 
		fi
		;;
	    ext2 )
		if test "x$dry_run" = "xno"; then
		    log_wrap mke2fs $filesystem_options "$full_dev"
		fi
		add_rpm e2fsprogs
		;;
	    reiserfs )
		if test "x$dry_run" = "xno"; then
		    echo y | log_wrap mkreiserfs -q -v 1 $filesystem_options "$full_dev" 
		fi
		add_rpm reiserfsprogs
		;;
	    jfs )
		if test "x$dry_run" = "xno"; then
		    log_wrap mkfs.jfs -f $filesystem_options "$full_dev"
		fi
		add_rpm jfsutils
		;;
	    ext3 )
		if test "x$dry_run" = "xno"; then
		    log_wrap mke2fs -j $filesystem_options "$full_dev"
		fi
		add_rpm e2fsprogs
	        ;;
	    xfs )
		if test "x$dry_run" = "xno"; then
		    log_wrap mkfs.xfs -f $filesystem_options "$full_dev"
		fi
		add_rpm xfsprogs
		;;
	    md )
	        # we doesn't need to do anything special about each drive
		# in raid array, except making sure we have raidtools...
		add_rpm raidtools
		;;
	    *)
		die "unknown filesystem type $filesystem"
		;;
	    esac
	fi
    done < /tmp/create_partitions.dev
done


# a) sort by dir
# b) mount in proper order
# c) create temporary fstab

log warn "`nls "Mounting destination filesystem"`"

if test "x$dry_run" = "xno"; then
  test -d /dest || mkdir -m 755 /dest
fi

echo -n >/tmp/fstab

# unfortunatelly busybox sort does not support fields
# let's reorganize file 
cat /tmp/create_partitions \
| while read device part size part_type filesystem format dir rest; do 
    echo "$dir $device $part $size $part_type $filesystem $format $rest"
done | sort >/tmp/create_partitions.by_dir

while read dir device part size part_type filesystem format options filesystem_options; do
    
    test "x$part_type" != "xextended" || continue
    # skip md partitions
    test "x$filesystem" != "xmd" || continue
    
    full_dev=`part ${device} ${part}`
    
    mode=""
    case "$dir" in
	"/tmp") mode=1777; ;;
	"/var/tmp") mode=1777; ;;
	"/var/lock") mode=751; ;;
	"/var/mail") mode=775; ;;
    esac

    if test "x$filesystem" = "xswap"; then
	log info "`nls "Activating swap %s" "$full_dev"`"
	if test "x$dry_run" = "xno"; then
	    swapon "$full_dev"
	fi
    else
	log info "`nls "Mounting partition %s on %s (type %s)" "$full_dev" "/dest$dir" "$filesystem"`"
	if test "x$dry_run" = "xno"; then
	    test -d "/dest${dir}" || mkdir -p -m ${mode:-755} "/dest${dir}"
	    mount -t "$filesystem" "$full_dev" "/dest${dir}"
	fi
    fi

    # TODO is fs_freq ok?
    # there is no point in checking xfs (fsck.xfs == exit 0), reiserfsck
    # also doesn't like to be called from fsck -A, I don't know about ext3
    # OTOH -- ext2 and jfs needs this.
    case "$filesystem" in
      ext2 | jfs )
        case "$dir" in
	  "/")  fs_passno=1; fs_freq=1; ;;
	  *)    fs_passno=2; fs_freq=1; ;;
        esac
	;;
      * )
        fs_passno=0
        fs_freq=0
    esac

    : ${options:?Error: options not set}
    # disabled, what was it for? Arn't modes deduced from FHS contest? --mal
    #test -z "$mode" || options="${options},mode=${mode}"

    echo -e "$full_dev\t${dir}\t${filesystem}\t${options}\t${fs_freq:-0}\t${fs_passno:-0}" >>/tmp/fstab
done < /tmp/create_partitions.by_dir

echo -e "proc\t/proc\tproc\tdefaults\t0\t0" >>/tmp/fstab
echo -e "pts\t/dev/pts\tdevpts\tgid=5,mode=600\t0\t0" >>/tmp/fstab
echo -e "/dev/fd0\t/mnt/floppy\tvfat\tnoauto\t0\t0" >>/tmp/fstab
echo -e "/dev/cdrom\t/mnt/cdrom\tiso9660\tnoauto,ro,user,unhide\t0\t0" >>/tmp/fstab


if test "x$dry_run" = "xno"; then
    test -d "/dest/etc" || mkdir -m 755 "/dest/etc"

    cp -f /tmp/fstab /dest/etc/
    chmod 644 /dest/etc/fstab

    if test -f /tmp/raidtab ; then
      cp -f /tmp/raidtab /dest/etc/
      chmod 640 /dest/etc/raidtab
    fi

    # genintrd needs /etc/modules.conf
    if test -f /etc/modules.conf; then 
	cp -f /etc/modules.conf /dest/etc/ 
    else
	echo -n >/dest/etc/modules.conf
    fi
    chmod 640 /dest/etc/modules.conf

    # move log to dest
    if test -f /var/log/installer; then 
	test -d /dest/var/log || mkdir -p /dest/var/log
	mv /var/log/installer /dest/var/log/
	ln -s /dest/var/log/installer /var/log/installer
	# just in case...
	chmod 600 /dest/var/log/installer
    fi
fi

if test "x$dry_run" = "xno"; then
    log warn "`nls 'Dest filesystem created in /dest directory'`"
else
    log warn 'Dest filesystem *NOT* created (export dry_run=no and run once again)'
fi

# vim:ft=sh
