
readonly XID_DISK="DISK"

#
#
#
#
readonly PART_TAB_PREFIX="${XID_DISK}_PTAB" # prefix for part. table variables

readonly PART_FIRST="a"			# first valid partition letter
readonly PART_LAST="h"			# last partition letter (inclusive)
readonly PART_WHOLEDISK="c"		# partition stretching entire disk

readonly DEF_SECTOR_OFFS=2048		# skip sectors from begin of disk;
readonly MIN_DISK_SIZE=1024		# min. supported disk size (in MB)

readonly FS_UNUSED=0			# filesystem types used by sdisklabel
readonly FS_SWAP=1
readonly FS_EXT2=8

readonly FDISK="/sbin/fdisk"
readonly SDISKLABEL="/sbin/sdisklabel"
readonly SG_VPD="/usr/bin/sg_vpd"



convert_to_bytes()
{
	convert_disk_size "$1" "B" | cut -d' ' -f1
}

convert_disk_size()
{
	local size=`trim "$1"`
	local to_unit=`trim "$2"`
	local from_unit=""
	local -i from_factor=0
	local -i to_factor=0
	local -i from_val=0
	local -i to_val=0

	if test -n "$to_unit"; then
		to_unit=`get_size_unit "0 $to_unit"`
		if test $? -ne 0; then
			debug "invalid conversion unit: $to_unit"
			return 2
		fi
	fi

	from_val=`get_size_value "$size"`	# also checks unit
	if test $? -ne 0; then
		debug "invalid size parameter: $size"
		return 2
	fi
	from_unit=`get_size_unit "$size"`
	from_factor=`get_size_unit_factor "$from_unit"`

	if test -n "$to_unit"; then		# explicit unit
		to_factor=`get_size_unit_factor "$to_unit"`

		if test $from_factor -le $to_factor; then	# E.g., K -> M
			to_val=$(($from_val / $(($to_factor / $from_factor))))
		else
			to_val=$(($from_val * $(($from_factor / $to_factor))))
		fi
	else					# auto-scaled unit
		for to_unit in T G M K B; do
			to_factor=`get_size_unit_factor "$to_unit"`
			if test $to_factor -lt $from_factor; then
				to_val=0
				break		# no appropriate unit
			fi
			to_val=$(($from_val / $(($to_factor / $from_factor))))
			if test $to_val -gt 0; then
				break		# found fitting unit
			fi
		done
	fi

	echo "$to_val $to_unit"
}

get_size_value()
{
	local size=`trim "$1"`
	local -i ret=1

	validate_disk_size "$size"
	if test $? -eq 0; then
		echo $size | tr -c -d [0-9] 2>/dev/null
		ret=$?
	fi

	return $ret
}

get_size_unit()
{
	local size=`trim "$1"`
	local unit=""
	local -i ret=1

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

	validate_disk_size "$size"
	if test $? -eq 0; then
		unit=`echo $size | tr -c -d [A-Za-z] 2>/dev/null`
		if test -n "$unit"; then
			unit=`echo $unit | tr [a-z] [A-Z] 2>/dev/null`
			ret=$?
		else
			unit="B"
			ret=0
		fi
		echo $unit
	fi

	return $ret
}

get_size_unit_factor()
{
	local unit=`trim "$1"`
	local -i factor=0

	case "$unit" in
	b|B) factor=1 ;;
	k|K) factor=1024 ;;
	m|M) factor=1048576 ;;
	g|G) factor=1073741824 ;;
	t|T) factor=1099511627776 ;;
	*)   factor=0 ;;
	esac

	echo $factor
}

get_disk_space()
{
	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local size=""
	local -i cap=0
	local -i ret=1

	[ -n "$devpath" ] || { error "no device specified"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device";      return 2; }

	eval size=\${${PART_TAB_PREFIX}_${dev}_size}
	validate_disk_size "$size"
	if test $? -eq 0; then
		cap=`convert_disk_size "$size" "K" | cut -d' ' -f1` # to 1k blks
	else
		if test ! -x "$FDISK"; then
			error "$FDISK: command not found"
			return 1
		fi
		cap=`$FDISK -s "$devpath" 2>/dev/null`	# in 1k blocks
	fi
	if test $? -eq 0; then
		ret=0
	fi

	echo "$(($cap / 1024)) M"		# to 1M blocks
	return $ret
}

get_free_disk_space()
{
	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local parts=""
	local part=""
	local size=""
	local total=""
	local free=""
	local -i used=0
	local -i ret=1

	[ -n "$devpath" ] || { error "no device specified"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device";      return 2; }

	eval free=\${${PART_TAB_PREFIX}_${dev}_free}
	if test -z "$free"; then
		total=`get_disk_space "$devpath"`
		if test $? -eq 0; then
			total=`convert_to_bytes "$total"`

			eval parts=\${${PART_TAB_PREFIX}_${dev}}
			parts=${parts/$PART_WHOLEDISK/}  # skip 'c' partition
			for part in $parts; do
				eval size=\${${PART_TAB_PREFIX}_${dev}_${part}_size}
				size=`convert_to_bytes "$size"`
				used=$(($used + $size))
			done
			free=$(($total - $used))
		fi
	fi

	convert_disk_size "$free" "M"
	return $?
}

validate_disk_size()
{
	local size=`trim "$1"`
	local result=""
	local -i ret=1

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

	result=`echo $size | \
	  sed -ne 's@^\([0-9]\+\)[ \t]*\([bBkKmMgGtT]\?\)$@\1 \2@p' 2>/dev/null`
	if test -n "$result"; then
		return 0
	else
		return 1
	fi
}

get_disk_devices()
{
	local ptab_vars=""
	local ptab_defs=""
	local devs=""
	local dev=""

	eval ptab_vars=\${!${PART_TAB_PREFIX}_*}
	ptab_defs=${ptab_vars//${PART_TAB_PREFIX}_/}
	devs=`for dev in $ptab_defs; do echo $dev; done | grep -v _ 2>/dev/null`

	echo $devs
}

get_device_path()
{
	local dev=`trim "$1"`
	local devpath=""
	local d=""

	[ -n "$dev" ] || { debug "no device name specified"; return 2; }

	for d in $INV_DISKS; do
		if test "${d##*/}" = "$dev"; then
			devpath=$d
			break
		fi
	done

	echo $devpath
	test -n "$devpath"	# result: device exists or not
}

get_disk_id()
{
	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local model=""
	local vendor=""
	local id=""

	[ -n "$devpath" ] || { error "no device specified"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device";      return 2; }

	eval id=\${${PART_TAB_PREFIX}_${dev}_model}
	if test -z "$id"; then
		if test ! -x "$SG_VPD"; then
			error "$SG_VPD: command not found"
			return 1
		fi
		vendor=`$SG_VPD -q --page=-1 "$devpath" 2>/dev/null | \
			grep -i "Vendor_identification" | cut -d: -f2`
		vendor=`trim "$vendor"`
		model=`$SG_VPD -q --page=-1 "$devpath" 2>/dev/null | \
			grep -i "Product_identification" | cut -d: -f2`
		model=`trim "$model"`
		id="$vendor $model"
	fi

	echo $id
}




pinpoint_partition()
{
	local mpnt=`trim "$1"`
	local dev=""
	local parts=""
	local part=""
	local mnt=""

	[ -n "$mpnt" ] || { debug "no mountpoint specified"; return 2; }

	for dev in `get_disk_devices`; do
		eval parts=\${${PART_TAB_PREFIX}_${dev}}
		for part in $parts; do
			eval mnt=\${${PART_TAB_PREFIX}_${dev}_${part}_mnt}
			mnt=`trim "$mnt"`
			if test "$mnt" = "$mpnt"; then
				echo "$dev $part"
				return 0
			fi
		done
	done

	return 1
}

is_partition_defined()
{
	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local part=`trim "$2"`
	local parts=""

	[ -n "$devpath" ] || { error "no device specified"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device";      return 2; }

	validate_partition "$part"
	if test $? -ne 0; then
		debug "invalid partition letter: $part"
		return 2
	fi

	eval parts=\${${PART_TAB_PREFIX}_${dev}}
	expr index "$parts" "$part" >/dev/null 2>&1
}

partition_letter_to_number()
{
	local part=`trim "$1"`
	local base=`trim "$2"`
	local -i part_val=0
	local -i base_part_val=0
	local -i ret=1

	base=${base:-0}
	is_number "$base" || { debug "partition base NaN: $base"; return 2; }

	validate_partition "$part"
	if test $? -eq 0; then
		#
		#
		part_val=`echo -n $part | od -An -td1 2>/dev/null`
		base_part_val=`echo -n $PART_FIRST | od -An -td1 2>/dev/null`

		if is_number "$part_val" && is_number "$base_part_val"; then
			echo $(($part_val - $base_part_val + $base))
			ret=0
		fi
	fi

	return $ret
}

partition_number_to_letter()
{
	local num=`trim "$1"`
	local base=`trim "$2"`
	local part=""
	local -i base_part_val=0
	local -i part_val_last=0
	local -i part_hex=0
	local -i ret=1

	base=${base:-0}
	is_number "$num"   || { debug "partition number NaN: $num"; return 2; }
	is_number "$base"  || { debug "partition base NaN: $base";  return 2; }

	[ $num -ge $base ] || { debug "partition number below base"; return 1; }

	base_part_val=`echo -n $PART_FIRST | od -An -td1 2>/dev/null`
	part_val_last=`echo -n $PART_LAST  | od -An -td1 2>/dev/null`

	part_val=$(($num - $base + $base_part_val))
	if test $part_val -le $part_val_last; then
		part_hex=`printf %x $part_val`
		echo -e "\x$part_hex"		# kind of itoa()
		ret=0
	fi

	return $ret
}

define_wholedisk_partition()
{
	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local parts=""
	local disk_size=""

	[ -n "$devpath" ] || { error "no device specified"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device";      return 2; }

	eval parts=\${${PART_TAB_PREFIX}_${dev}}

	#
	#
	is_partition_defined "$devpath" "$PART_WHOLEDISK"
	if test $? -ne 0; then
		eval ${PART_TAB_PREFIX}_${dev}=\"$parts $PART_WHOLEDISK\"
	fi
	disk_size=`get_disk_space "$devpath"`	# in MB
	eval ${PART_TAB_PREFIX}_${dev}_${PART_WHOLEDISK}_size=\"$disk_size\"
	eval ${PART_TAB_PREFIX}_${dev}_${PART_WHOLEDISK}_type=0

	return 0
}

validate_partition()
{
	local part=`trim "$1"`
	
	expr match "$part" "^[$PART_FIRST-$PART_LAST]$" >/dev/null 2>&1
	if test $? -eq 0; then
		return 0
	else
		return 1
	fi
}

validate_partition_type()
{
	local type=`trim "$1"`
	
	expr match "$type" "^[0-9A-Fa-f][0-9A-Fa-f]\?$" >/dev/null 2>&1
	if test $? -eq 0; then
		return 0
	else
		return 1
	fi
}

partition_defaults()
{

	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local -i dsk_size=0		# in MB
	local -i mem_size=0		# in MB
	local -i size_ldr=0
	local -i size_a=0
	local -i size_b=0
	local -i size_c=0
	local -i size_d=0
	local -i size_e=0
	local -i size_f=0
	local -i size_g=0
	local -i free=0			# remaining disk space (in MB)

	[ -n "$devpath" ] || { error "no device specified"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device";      return 2; }

	dsk_size=`get_disk_space "$devpath" | cut -d' ' -f1`
	if test $dsk_size -lt $MIN_DISK_SIZE; then
		error "$devpath: insufficient capacity for default configuration"
		return 1
	fi
	mem_size=$INV_MEM
	if test $mem_size -eq 0; then
		error "unknown memory size; cannot load default configuration"
		return 1
	fi

	#
	#
	free=$dsk_size					# no space used yet

	size_ldr=$(($DEF_SECTOR_OFFS * 512 / 1024))	# in kB
	size_ldr=$((($size_ldr + 1023) / 1024))		# in MB and rounded up
	free=$(($free - $size_ldr))

	size_a=128
	free=$(($free - $size_a))

	size_b=$mem_size
	if test $size_b -lt 32; then
		size_b=32
	elif test $size_b -gt 1024; then
		size_b=1024
	fi
	free=$(($free - $size_b))

	size_c=$dsk_size

	size_d=$(($dsk_size / 10))
	if test $size_d -lt 100; then
		size_d=100
	elif test $size_d -gt 1024; then
		size_d=1024
	fi
	free=$(($free - $size_d))

	size_e=$(($dsk_size / 5))
	if test $size_e -lt 375; then
		size_e=375
	fi
	free=$(($free - $size_e))

	size_g=$(($dsk_size / 20))
	if test $size_g -lt 50; then
		size_g=50
	elif test $size_g -gt 2560; then
		size_g=2560
	fi
	free=$(($free - $size_g))

	size_f=$(($dsk_size / 4))
	if test $size_f -lt 280; then
		size_f=280
	elif test $size_f -gt 2048; then
		size_f=2048
	fi
	free=$(($free - $size_f))

	if test $free -ge 0; then
		eval ${PART_TAB_PREFIX}_${dev}=\"a b c d e f g\"
		eval ${PART_TAB_PREFIX}_${dev}_a_mnt=\"/\"
		eval ${PART_TAB_PREFIX}_${dev}_a_type=\"$FS_EXT2\"
		eval ${PART_TAB_PREFIX}_${dev}_a_size=\"$size_a M\"
		eval ${PART_TAB_PREFIX}_${dev}_b_mnt=\"\"
		eval ${PART_TAB_PREFIX}_${dev}_b_type=\"$FS_SWAP\"
		eval ${PART_TAB_PREFIX}_${dev}_b_size=\"$size_b M\"
		eval ${PART_TAB_PREFIX}_${dev}_c_mnt=\"\"
		eval ${PART_TAB_PREFIX}_${dev}_c_type=\"$FS_UNUSED\"
		eval ${PART_TAB_PREFIX}_${dev}_c_size=\"$size_c M\"
		eval ${PART_TAB_PREFIX}_${dev}_d_mnt=\"/tmp\"
		eval ${PART_TAB_PREFIX}_${dev}_d_type=\"$FS_EXT2\"
		eval ${PART_TAB_PREFIX}_${dev}_d_size=\"$size_d M\"
		eval ${PART_TAB_PREFIX}_${dev}_e_mnt=\"/usr\"
		eval ${PART_TAB_PREFIX}_${dev}_e_type=\"$FS_EXT2\"
		eval ${PART_TAB_PREFIX}_${dev}_e_size=\"$size_e M\"
		eval ${PART_TAB_PREFIX}_${dev}_f_mnt=\"/home\"
		eval ${PART_TAB_PREFIX}_${dev}_f_type=\"$FS_EXT2\"
		eval ${PART_TAB_PREFIX}_${dev}_f_size=\"$size_f M\"
		eval ${PART_TAB_PREFIX}_${dev}_g_mnt=\"/var\"
		eval ${PART_TAB_PREFIX}_${dev}_g_type=\"$FS_EXT2\"
		eval ${PART_TAB_PREFIX}_${dev}_g_size=\"$size_g M\"

		return 0
	else
		error "$devpath: default partition layout won't fit"
		return 1
	fi
}

load_partition_config()
{
	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local parts=""
	local part=""
	local part_size=""
	local old_IFS="$IFS"
	local -i part_type=0
	local -i ret=0
	local -a part_tab
	local -a part_cfg

	[ -b "$devpath" ] || { debug "$devpath: not a block device"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device"; return 2; }
	[ -x "$SDISKLABEL" ] || { error "$SDISKLABEL: command not found"; return 1; }

	eval unset -v \${\!${PART_TAB_PREFIX}_${dev}*}

	#
	#
	IFS=$'\n'
	part_tab=(`$SDISKLABEL "$devpath" print 2>/dev/null | grep ^partition`)
	IFS="$old_IFS"
	#
	#
	#
	for line in "${part_tab[@]}"; do
		part_cfg=(`echo $line | tr -c [0-9] ' '`)
		part=`partition_number_to_letter "${part_cfg[0]}"`
		if test $? -ne 0; then
			debug "ignoring invalid partition: #${part_cfg[0]}"
			ret=1
			continue	# ignore invalid partition letters
		fi
		parts="$parts $part"
		eval ${PART_TAB_PREFIX}_${dev}=\"$parts\"
		part_size=$((${part_cfg[3]} * 512))	# now in bytes
		part_size=`convert_disk_size "$part_size"`
		eval ${PART_TAB_PREFIX}_${dev}_${part}_size=\"$part_size\"
		eval ${PART_TAB_PREFIX}_${dev}_${part}_type=\"${part_cfg[1]}\"
	done

	if test -z "$parts"; then
		ret=1
	fi
	return $ret
}

verify_partition_config()
{
	local dev=`trim "$1"`
	local devpath=""
	local fspath=""		# path on filesystem
	local cfpath=""		# path from config
	local parts=""
	local part=""
	local size=""
	local -i type=0

	[ -n "$dev" ] || { debug "no disk device"; return 1; }

	devpath=`get_device_path "$dev"`
	if test $? -eq 0; then
		eval cfpath=\${${PART_TAB_PREFIX}_${dev}_path}
		cfpath=`normalize_pathname "$cfpath"`
		fspath=`normalize_pathname "${devpath%/*}"`
		if test "$fspath" != "$cfpath"; then
			error "$dev: conflicting device paths; configuration file indicates \`$cfpath', filesystem indicates \`$fspath'"
			return 1
		fi
	else
		error "$dev: device file not found"
		return 1
	fi

	eval parts=\${${PART_TAB_PREFIX}_${dev}}

	for part in $parts; do
		validate_partition "$part"
		if test $? -ne 0; then
			error "$part: invalid partition letter"
			return 1
		fi

		eval size=\${${PART_TAB_PREFIX}_${dev}_${part}_size}
		validate_disk_size "$size"
		if test $? -ne 0; then
			error "$size: invalid disk size"
			return 1
		fi

		eval type=\${${PART_TAB_PREFIX}_${dev}_${part}_type}
		validate_partition_type "$type"
		if test $? -ne 0; then
			error "$type: invalid partition type"
			return 1
		fi
	done

	return 0
}

partition_disk()
{
	local devpath=`trim "$1"`
	local dev=${devpath##*/}
	local parts=""
	local part=""
	local part_size=""		# string, could have multiplier letter
	local -i part_type=0
	local -i part_num=0
	local -i part_sect=0
	local -i sect_offs=0
	local -i ret=0

	[ -b "$devpath" ] || { debug "$devpath: not a block device"; return 2; }
	[ -n "$dev"     ] || { debug "no disk device"; return 2; }
	[ -x "$SDISKLABEL" ] || { error "$SDISKLABEL: command not found"; return 1; }

	define_wholedisk_partition "$devpath"
	if test $? -ne 0; then
		error "$dev: failed defining whole-disk partition"
		return 1
	fi

	verify_partition_config "$dev"
	if test $? -ne 0; then
		error "$dev: partition configuration contains error(s)"
		return 1
	fi

	eval parts=\${${PART_TAB_PREFIX}_${dev}}
	parts=`sort_list "$parts"`

	sect_offs=$DEF_SECTOR_OFFS

	#
	#
	$DRY_RUN $SDISKLABEL $devpath zero
	if test $? -ne 0; then
		error "$dev: failed zero'ing disk"
		return 1
	fi

	for part in $parts; do
		part_num=`partition_letter_to_number "$part"`
		eval part_type=\${${PART_TAB_PREFIX}_${dev}_${part}_type}
		eval part_size=\${${PART_TAB_PREFIX}_${dev}_${part}_size}
		part_sect=`convert_to_bytes "$part_size"`
		part_sect=$(($part_sect / 512))		# to sectors

		if test "$part" = "$PART_WHOLEDISK"; then
			$DRY_RUN $SDISKLABEL $devpath $part_num \
						 0 $part_sect \
						 $part_type
			ret=$?
		else
			$DRY_RUN $SDISKLABEL $devpath $part_num \
						 $sect_offs $part_sect \
						 $part_type
			ret=$?

			sect_offs=$(($sect_offs + $part_sect))
		fi

		if test $ret -ne 0; then
			error "$dev: failed creating partition: $part"
			error "$dev: attempted to create partition of size '$part_size' and type '$part_type' on $devpath"
			break
		fi
	done

	return $ret
}


create_fstab()
{
	local fstab=`trim "$1"`
	local mprefix=`trim "$2"`
	local dev=""
	local devpath=""
	local parts=""
	local part=""
	local part_type=""
	local part_mnt=""
	local mpnt=""
	local mntopts=""
	local vfstype=""
	local -i part_num=0
	local -i pass=0
	local -i ret=1

	[ -n "$fstab" ] || { debug "no fstab file specified"; return 2; }

	$DRY_RUN mkdirhier `dirname "$fstab" 2>/dev/null` || return 1

	>$fstab
	
	for dev in `get_disk_devices`; do
		eval parts=\${${PART_TAB_PREFIX}_${dev}}
		for part in $parts; do
			if test "$part" = "$PART_WHOLEDISK"; then
				continue
			fi

			part_num=`partition_letter_to_number "$part" "1"`

			eval part_type=\${${PART_TAB_PREFIX}_${dev}_${part}_type}
			eval part_mnt=\${${PART_TAB_PREFIX}_${dev}_${part}_mnt}

			devpath="/dev/${dev}${part_num}"
			if test "$part_type" -eq "$FS_EXT2"; then
				mpnt="${mprefix%%/}/${part_mnt##/}"
			else
				mpnt="none"
			fi
			mntopts=`get_fs_mntops "$part_mnt"`
			pass=`get_fs_passno "$part_mnt"`
			vfstype=`get_fs_vfstype "$part_mnt" "$part_type"`

			echo -e "$devpath\t$mpnt\t$vfstype\t$mntopts\t0 $pass" >>$fstab
			ret=$?
		done
	done

	if test $ret -ne 0; then
		rm -f "$fstab"
	fi
	
	return $ret
}

get_fs_mntops()
{
	local mpnt=`trim "$1"`
	local opts="defaults"

	case "$mpnt" in
	"/")    opts="defaults,noauto" ;;
	"/tmp") opts="auto,async,rw,nodev,nosuid" ;;
	"/var") opts="noatime,nodiratime" ;;
	esac

	echo $opts
}

get_fs_passno()
{
	local mpnt=`trim "$1"`
	local pass=0

	if test "$mpnt" = "/"; then
		pass=1
	else
		pass=2
	fi

	echo $pass
}

get_fs_vfstype()
{
	local mpnt=`trim "$1"`
	local part_type=`trim "$2"`
	local vfstype=""

	if test $part_type -eq $FS_EXT2; then
		case "$mpnt" in
		"/boot") vfstype="ext2" ;;
		*)       vfstype="ext3" ;;
		esac
	elif test $part_type -eq $FS_SWAP; then
		if test -n "$mpnt"; then
			warn "mountpoint defined for swap device"
		fi
		vfstype="swap"
	else
		vfstype="ignore"
	fi
	
	echo $vfstype
}
