#!/bin/ksh93

# file: /data/httpd/htdocs/intern/ubuntu-part.sh

typeset -r VERSION='1.0' FPROG=${.sh.file} PROG=${FPROG##*/} SDIR=${FPROG%/*}
unset LANG 
export LC_CTYPE=C LC_NUMERIC=C LC_MESSAGES=C LC_COLLATE=C LC_TIME=C

function showUsage {
	[[ -n $1 ]] && X='-?' ||  X='--man'
	getopts -a ${PROG} "${ print ${USAGE} ; }" OPT $X
}

typeset -A CODE2GUID GUID2CODE
function AddType {
	typeset K=$1
	K="_${K#0x}"
	CODE2GUID[$K]="$2:$3"
	GUID2CODE["$2"]="$K"
}

# extracted from gptfdisk-1.0.1 via:
# grep ' AddType' parttypes.cc | sed -e 's,;.*,,' -e 's,root (/),/,' \
#	-e 's/[),]//g' -e 's/(/ /' -e 's/^ *//' -e 's,",@,g' -e "s,@,',g"
# see also: sfdisk -T
AddType 0x0000 '00000000-0000-0000-0000-000000000000' 'Unused entry' 0
AddType 0x0100 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x0400 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x0600 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x0700 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 1
AddType 0x0b00 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x0c00 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x0c01 'E3C9E316-0B5C-4DB8-817D-F92DF00215AE' 'Microsoft reserved'
AddType 0x0e00 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x1100 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x1400 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x1600 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x1700 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x1b00 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x1c00 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x1e00 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' 'Microsoft basic data' 0
AddType 0x2700 'DE94BBA4-06D1-4D40-A16A-BFD50179D6AC' 'Windows RE'
AddType 0x3000 '7412F7D5-A156-4B13-81DC-867174929325' 'ONIE boot'
AddType 0x3001 'D4E6E2CD-4469-46F3-B5CB-1BFF57AFC149' 'ONIE config'
AddType 0x3900 'C91818F9-8025-47AF-89D2-F030D7000C2C' 'Plan 9'
AddType 0x4100 '9E1A2D38-C612-4316-AA26-8B49521E5A8B' 'PowerPC PReP boot'
AddType 0x4200 'AF9B60A0-1431-4F62-BC68-3311714A69AD' 'Windows LDM data'
AddType 0x4201 '5808C8AA-7E8F-42E0-85D2-E1E90434CFB3' 'Windows LDM metadata'
AddType 0x4202 'E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D' 'Windows Storage Spaces'
AddType 0x7501 '37AFFC90-EF7D-4E96-91C3-2D7AE055B174' 'IBM GPFS'
AddType 0x7f00 'FE3A2A5D-4F32-41A7-B725-ACCC3285A309' 'ChromeOS kernel'
AddType 0x7f01 '3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC' 'ChromeOS root'
AddType 0x7f02 '2E0A753D-9E48-43B0-8337-B15192CB1B5E' 'ChromeOS reserved'
AddType 0x8200 '0657FD6D-A4AB-43C4-84E5-0933C84B4F4F' 'Linux swap'
AddType 0x8300 '0FC63DAF-8483-4772-8E79-3D69D8477DE4' 'Linux filesystem'
AddType 0x8301 '8DA63339-0007-60C0-C436-083AC8230908' 'Linux reserved'
AddType 0x8302 '933AC7E1-2EB4-4F13-B844-0E14E2AEF915' 'Linux /home'
AddType 0x8303 '44479540-F297-41B2-9AF7-D131D5F0458A' 'Linux x86 /'
AddType 0x8304 '4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709' 'Linux x86-64 /'
AddType 0x8305 'B921B045-1DF0-41C3-AF44-4C6F280D3FAE' 'Linux ARM64 /'
AddType 0x8306 '3B8F8425-20E0-4F3B-907F-1A25A76F98E8' 'Linux /srv'
AddType 0x8307 '69DAD710-2CE4-4E3C-B16C-21A1D49ABED3' 'Linux ARM32 /'
AddType 0x8400 'D3BFE2DE-3DAF-11DF-BA40-E3A556D89593' 'Intel Rapid Start'
AddType 0x8e00 'E6D6D379-F507-44C2-A23C-238F2A3DF928' 'Linux LVM'
AddType 0xa500 '516E7CB4-6ECF-11D6-8FF8-00022D09712B' 'FreeBSD disklabel'
AddType 0xa501 '83BD6B9D-7F41-11DC-BE0B-001560B84F0F' 'FreeBSD boot'
AddType 0xa502 '516E7CB5-6ECF-11D6-8FF8-00022D09712B' 'FreeBSD swap'
AddType 0xa503 '516E7CB6-6ECF-11D6-8FF8-00022D09712B' 'FreeBSD UFS'
AddType 0xa504 '516E7CBA-6ECF-11D6-8FF8-00022D09712B' 'FreeBSD ZFS'
AddType 0xa505 '516E7CB8-6ECF-11D6-8FF8-00022D09712B' 'FreeBSD Vinum/RAID'
AddType 0xa580 '85D5E45A-237C-11E1-B4B3-E89A8F7FC3A7' 'Midnight BSD data'
AddType 0xa581 '85D5E45E-237C-11E1-B4B3-E89A8F7FC3A7' 'Midnight BSD boot'
AddType 0xa582 '85D5E45B-237C-11E1-B4B3-E89A8F7FC3A7' 'Midnight BSD swap'
AddType 0xa583 '0394Ef8B-237E-11E1-B4B3-E89A8F7FC3A7' 'Midnight BSD UFS'
AddType 0xa584 '85D5E45D-237C-11E1-B4B3-E89A8F7FC3A7' 'Midnight BSD ZFS'
AddType 0xa585 '85D5E45C-237C-11E1-B4B3-E89A8F7FC3A7' 'Midnight BSD Vinum'
AddType 0xa600 '824CC7A0-36A8-11E3-890A-952519AD3F61' 'OpenBSD disklabel'
AddType 0xa800 '55465300-0000-11AA-AA11-00306543ECAC' 'Apple UFS'
AddType 0xa900 '516E7CB4-6ECF-11D6-8FF8-00022D09712B' 'FreeBSD disklabel' 0
AddType 0xa901 '49F48D32-B10E-11DC-B99B-0019D1879648' 'NetBSD swap'
AddType 0xa902 '49F48D5A-B10E-11DC-B99B-0019D1879648' 'NetBSD FFS'
AddType 0xa903 '49F48D82-B10E-11DC-B99B-0019D1879648' 'NetBSD LFS'
AddType 0xa904 '2DB519C4-B10F-11DC-B99B-0019D1879648' 'NetBSD concatenated'
AddType 0xa905 '2DB519EC-B10F-11DC-B99B-0019D1879648' 'NetBSD encrypted'
AddType 0xa906 '49F48DAA-B10E-11DC-B99B-0019D1879648' 'NetBSD RAID'
AddType 0xab00 '426F6F74-0000-11AA-AA11-00306543ECAC' 'Recovery HD'
AddType 0xaf00 '48465300-0000-11AA-AA11-00306543ECAC' 'Apple HFS/HFS+'
AddType 0xaf01 '52414944-0000-11AA-AA11-00306543ECAC' 'Apple RAID'
AddType 0xaf02 '52414944-5F4F-11AA-AA11-00306543ECAC' 'Apple RAID offline'
AddType 0xaf03 '4C616265-6C00-11AA-AA11-00306543ECAC' 'Apple label'
AddType 0xaf04 '5265636F-7665-11AA-AA11-00306543ECAC' 'AppleTV recovery'
AddType 0xaf05 '53746F72-6167-11AA-AA11-00306543ECAC' 'Apple Core Storage'
AddType 0xbc00 '0311FC50-01CA-4725-AD77-9ADBB20ACE98' 'Acronis Secure Zone'
AddType 0xbe00 '6A82CB45-1DD2-11B2-99A6-080020736631' 'Solaris boot'
AddType 0xbf00 '6A85CF4D-1DD2-11B2-99A6-080020736631' 'Solaris root'
AddType 0xbf01 '6A898CC3-1DD2-11B2-99A6-080020736631' 'Solaris /usr & Mac ZFS'
AddType 0xbf02 '6A87C46F-1DD2-11B2-99A6-080020736631' 'Solaris swap'
AddType 0xbf03 '6A8B642B-1DD2-11B2-99A6-080020736631' 'Solaris backup'
AddType 0xbf04 '6A8EF2E9-1DD2-11B2-99A6-080020736631' 'Solaris /var'
AddType 0xbf05 '6A90BA39-1DD2-11B2-99A6-080020736631' 'Solaris /home'
AddType 0xbf06 '6A9283A5-1DD2-11B2-99A6-080020736631' 'Solaris alternate sector'
AddType 0xbf07 '6A945A3B-1DD2-11B2-99A6-080020736631' 'Solaris Reserved 1'
AddType 0xbf08 '6A9630D1-1DD2-11B2-99A6-080020736631' 'Solaris Reserved 2'
AddType 0xbf09 '6A980767-1DD2-11B2-99A6-080020736631' 'Solaris Reserved 3'
AddType 0xbf0a '6A96237F-1DD2-11B2-99A6-080020736631' 'Solaris Reserved 4'
AddType 0xbf0b '6A8D2AC7-1DD2-11B2-99A6-080020736631' 'Solaris Reserved 5'
AddType 0xc001 '75894C1E-3AEB-11D3-B7C1-7B03A0000000' 'HP-UX data'
AddType 0xc002 'E2A1E728-32E3-11D6-A682-7B03A0000000' 'HP-UX service'
AddType 0xea00 'BC13C2FF-59E6-4262-A352-B275FD6F7172' 'Freedesktop $BOOT'
AddType 0xeb00 '42465331-3BA3-10F1-802A-4861696B7521' 'Haiku BFS'
AddType 0xed00 'F4019732-066E-4E12-8273-346C5641494F' 'Sony system partition'
AddType 0xed01 'BFBFAFE7-A34F-448A-9A5B-6213EB736C22' 'Lenovo system partition'
AddType 0xef00 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' 'EFI System'
AddType 0xef01 '024DEE41-33E7-11D3-9D69-0008C781F39F' 'MBR partition scheme'
AddType 0xef02 '21686148-6449-6E6F-744E-656564454649' 'BIOS boot partition'
AddType 0xf800 '4FBD7E29-9D25-41B8-AFD0-062C0CEFF05D' 'Ceph OSD'
AddType 0xf801 '4FBD7E29-9D25-41B8-AFD0-5EC00CEFF05D' 'Ceph dm-crypt OSD'
AddType 0xf802 '45B0969E-9B03-4F30-B4C6-B4B80CEFF106' 'Ceph journal'
AddType 0xf803 '45B0969E-9B03-4F30-B4C6-5EC00CEFF106' 'Ceph dm-crypt journal'
AddType 0xf804 '89C57F98-2FE5-4DC0-89C1-F3AD0CEFF2BE' 'Ceph disk in creation'
AddType 0xf805 '89C57F98-2FE5-4DC0-89C1-5EC00CEFF2BE' 'Ceph dm-crypt disk in creation'
AddType 0xfb00 'AA31E02A-400F-11DB-9590-000C2911D1B8' 'VMWare VMFS'
AddType 0xfb01 '9198EFFC-31C0-11DB-8F78-000C2911D1B8' 'VMWare reserved'
AddType 0xfc00 '9D275380-40AD-11DB-BF97-000C2911D1B8' 'VMWare kcore crash protection'
AddType 0xfd00 'A19D880F-05FC-4D3B-A006-743F0F84911E' 'Linux RAID'

function listGUIDs {
	typeset K V
	for K in ${!CODE2GUID[@]} ; do
		V="${CODE2GUID[$K]}"
		print "${K:1}  ${V%%:*}  ${.sh.match:1}"
	done
}

typeset -T Part_t=(
	typeset -ih 'partition number' n=-1
	typeset -lih '# of the first logical sector of the partion' start=-1
	typeset -lih '# of the last logical sector of the partion (incl.)' end=-1
	typeset -h 'partition type (GUID|parted fs-name|sgdisk code)' type=
	typeset -h 'partition name' name=
	typeset -h 'partition attrs' attrs=
	function getSize {
		integer -li S
		(( S=_.end - _.start + 1 ))
		print -- "$S"
	}
	typeset -fh 'Get the partition size in logical blocks' getSize
	function show {
		typeset T='-- (0000)'
		if [[ -n ${_.type} ]]; then
			T="${CODE2GUID[${_.type}]}"
			[[ -n $T ]] && T="${T#*:} (${_.type:2})" || T="??? (${_.type})"
		fi
		print "${_.n}  ${_.start}s..${_.end}s (size=${ _.getSize ; }s)  $T ${_.name}  ${_.attrs:--}"
	}
	typeset -fh 'Show the summary info for this partition' show
)

typeset -T  Disk_t=(
	typeset -lih 'size in logical sectors' sz=-1
	typeset -ih 'logical sector size in byte' lssz=-1
	typeset -ih 'physical sector size in byte' pssz=-1
	typeset -h 'partition table type' ptt=
	typeset -h 'device path' devpath=
	typeset -h 'device alias' devalias=
	typeset -h 'the device path for the partition containing the zfs' zfs=
	Part_t -a -h 'partition infos' parts

	function show {
		typeset -lui M=${_.sz}
		(( M=(M * _.lssz) >> 30 ))
		print "Label: ${_.ptt}\nSize: ${_.sz}s (~${M} GiB)" \
			"\nBlock size: ${_.lssz}/${_.pssz} (logical/physical)\n"

		# we use this because [0] usually contains garbage
		typeset -lui N
		for P in "${!_.parts[@]}" ; do
			X="M"
			N=${_.parts[P].getSize}
			(( N*=_.lssz ))
			(( N=N >> 20 ))
			(( N > 1023 )) && (( N=N >> 10 )) && X="G"
			print "${_.parts[P].show}  ${N} ${X}iB"
		done
	}
	typeset -fh 'Print disk and partition infos' show

	function add {
		typeset -n P=$1
		_.parts+=( P )
	}

	# with 512B log. block size (LBA):
	# 0x0000..0x01FF	MBR (e.g. with Grub)
	#	0x000	440B		Bootloader (Stage 1)
	#	0x1b8	  4B		Datenträgersignatur (seit Win2K)
	#	0x1bc	  2B		Null
	#	0x1be	 64B		Partition Table with 4 entries a 16B:
	#							0	1B	bootable (0x80) or not (0x00)
	#							1	3B	1st sector CHS address (absolut)
	#							4	1B	partition type (0x82 Linux swap, ...)
	#							5	3B	last sector CHS address (absolut)
	#							8	4B	start sector in LBA
	#							12	4B	size in LBA sectors
	#	0x1fe	  2B		Boot Signature = 55aa
	# 0x0200..0x03FF	GPT Header
	#	0x200	 8B			Signature 'EFI PART'
	#	0x208	 4B			Revision = 0000 0100
	#	0x20c	 4B			Header Size (usually 5c00 0000 = 92B)
	#	0x210	 4B			Header CRC32 sum
	#	0x214	 4B			reserved (zero filled)
	#	0x218	 8B			Position of this Header in LBAs (usually 1)
	#	0x220	 8B			Position of the Backup-Header in LBAs
	#	0x228	 8B			1st usable LBA for partitions (usually 34)
	#	0x230	 8B			last usable LBA for partitions (usually n/512-34)
	#	0x238	16B			Disk GUID aka UUID
	#	0x248	 8B			Starting LBA of part table entries = 2
	#	0x250	 4B			Number of part table entries
	#	0x254	 4B			Size of a each part table entry (usually 128B)
	#	0x258	 4B			part table CRC32 sum
	#	0x25c	420B		reserved, zero filled (sz differs for non-512B LBAs)
	# 0x0400..0x43FF	Partition Table
	#	0x400	16B			Partition type GUID
	#	0x410	16B			Unique partition GUID
	#	0x420	 8B			First LBA
	#	0x428	 8B			Last LBA (incl.)
	#	0x430	 8B			Partition Attribute Flags (bits):
	#						63-48	Defined/used by the individual part type
	#							63		MS: No drive letter aka do not automount
	#							62		MS:	Hidden
	#							61		MS: Shadow copy (of another partition)
	#							60		MS: Read-only
	#						47- 3	Reserved for future use
	#						    2	Legacy BIOS bootable
	#							1	EFI firmware should ignore this entry
	#							0	platform required: reserve as is
	#	0x438	72B			Partition name (36 UTF-16LE code units)
	#	0x480	128B		next entry
	#	...
	# 0x4400..n-0x4201	free space for partions (max. 128 in this case)
	# n-0x4200..n-0x201	backup aka secondary Partition Table
	# n-0x0200..n-1		backup aka secondary GPT Header
	function getRecommendedLayout {
		typeset -n DISK=$1
		typeset -li I N S O SPC LAST SWAP=$2 RESERVED CHUNK_SZ ZFS=$3
		typeset X
		(( RESERVED=8*1024*1024 / _.lssz ))	# reserved sectors (8 MiB)
		(( CHUNK_SZ=128*1024 / _.lssz ))		# sectors per md chunk
		
		(( ZFS )) && SWAP=0 || (( SWAP*=1024*1024*1024 / _.lssz ))

		DISK.devpath=${_.devpath}
		DISK.sz=${_.sz}
		DISK.lssz=${_.lssz}
		DISK.pssz=${_.pssz}
		DISK.ptt='gpt'
		DISK.zfs=
		[[ -z ${_.devalias} ]] && \
			DISK.devalias=${DISK.devpath} || DISK.devalias=${_.devalias}
		# At the end of the disk 1 sector for GPT header and at least 16K for
		# GPT entries are required (GPT backup) -> for 512B sectors == 33
		(( LAST=_.sz - 1 - 16 * 1024 / _.lssz - 1 ))

		# If later a HDD gets inserted, which has not exactly the same size as
		# this one, we need some space to adjust it. So reserve some space and
		# align it to the common chunk_sz
		Part_t R
		(( S=LAST - RESERVED ))
		(( N=S / CHUNK_SZ ))
		(( N*=CHUNK_SZ ))	# remaining sectors if aligned
		(( N != S )) && (( S=N + CHUNK_SZ ))
		(( S < 0 || LAST <= S  )) && return 1
		R.n=9 R.start=$S R.end=${LAST} R.type='_bf07' R.name= R.attrs=
		(( LAST=S-1 ))

		# At the start there should be enough space for 2 sectors (1xMBR,
		# 1xGPT header) + at least 16 KiB for GPT entries. However, we start
		# the 1st partition on sector 256 (128KiB), so that e.g. Grub can put
		# its 1.5 stage (30K) right after the MBR and use the 1st partition as
		# bios_grub for stage 2 or it might be used some day as UEFI partition -
		# therefore size == 256 MB.
		# Also starting at 128KiB guarantees alignment (even to chunk size).
		S=${CHUNK_SZ}
		I=1
		if (( BOOT )); then
			# BIOS GRUB/EFI partition - N * 256 MiB also assures 4k alignment.
			# For Solaris N=1 is ok. Linux images are hugh (with extra modules)
			# and thus ~ 2 images/256 MiB. So one may choose for desktop N=4.
			# However, at least on servers on should set 'MODULES=dep' in
			# /etc/initramfs-tools/initramfs.conf, should disable 
			# 'copy_modules_dir_filter' in
			# /usr/share/initramfs-tools/hooks/framebuffer-nvidia
			# and make sure via /etc/initramfs-tools/hooks/optimze-size that
			# unneed modules get copied to the image, like
			# lib/modules/*/kernel/drivers/gpu/drm/{amd,bochs,gma500,i915,radeon,vmwgfx}
			# and usr/lib/firmware/{amdgpu,i915,radeon}
			(( N=S + 1 * 256 * 1024 * 1024 / _.lssz - 1 ))
			(( S < 0 || N <= S  )) && return 1
			Part_t P
			P.n=1 P.start=$S P.end=$N P.type='_ef02' P.name=
			(( USE_EFI )) && P.type='_ef00' #|| P.attrs='2'
			DISK.add P
			(( S=N + 1 ))
			(( I++ ))
		fi

		# Data
		unset P ; Part_t P
		(( N=LAST - SWAP ))
		(( S < 0 || N <= S  )) && return 1
		P.n=$I P.start=$S P.end=${N} P.type='_8300' P.name= P.attrs=
		if (( USE_ZFS )); then
			P.type='_bf01'
			P.name='zfs'
			getPartPrefix X ${DISK.devalias} || \
				print -u2 "Assuming ZFS partition is $X"
			DISK.zfs="$X$I"
		fi
		DISK.add P

		# Swap
		if (( SWAP )); then
			unset P ; Part_t P
			(( S=N + 1 ))
			(( N=S + SWAP - 1 ))
			(( S < 0 || N <= S  )) && return 1
			P.n=3 P.start=$S P.end=$N P.type='_8200' P.name=
			DISK.add P
		fi
		DISK.add R
		return 0
	}
)

function getPartPrefix {
	typeset -n RES=$1
	typeset DEVALIAS="$2"			# arg <= ${DISK.devalias}
	typeset X=${DEVALIAS##*/}

	if [[ ${X:0:2} == 'sd' ]]; then
		RES="${DEVALIAS}"
	elif [[ ${X:0:4} == 'nvme' || ${X:0:4} == disk || $X =~ ^[pd][0-9] ]]; then
		RES="${DEVALIAS}p"
	else
		RES="${DEVALIAS}p"
		return 1
	fi
	return 0
}

FS2CODE=(
	['linux-swap(v0)']='_8200'
	['linux-swap(v1)']='_8200'
	['primary']='_8300'
	['logical']='_8300'
	['hfs']='_af00'
	['hfs+']='_af00'
	['hfsx']='_af00'
	['ext2']='_8300'
	['ext3']='_8300'
	['ext4']='_8300'
	['xfs']='_8300'
	['fat16']='_0600'
	['fat32']='_0b00'
	['ntfs']='_0700'
)
CODE2PATTR=(
	['_ef00']='boot'
	['_8200']='swap'
	['_fd01']='raid'
	['_8e00']='lvm'
	['_8400']='irst'
	['_ef02']='bios_grub'
)
# parted variant - sufficient but misses many details, like part type
function getDiskInfoParted {
	typeset -n DISK=$1
	integer K=0
	typeset -a INFO
	typeset LINE X
	X=${ (( $3 )) && cat "$2" || ${PARTED} -m "$2" u s p ; }
	DISK.devpath="$2"
	print "$X" | while read LINE ; do
		if (( K == 1 )); then
			INFO=( ${LINE//:/ } )
			DISK.devpath=${INFO[0]}	# real device
			DISK.sz=${INFO[1]%s}	# size in sectors (logical)
			# 2						# devtype (scsi|ide|md|...)
			DISK.lssz=${INFO[3]}	# byte/s (logical)
			DISK.pssz=${INFO[4]}	# byte/s (physical)
			DISK.ptt=${INFO[5]}		# part table type
			# 6						# HDD model
			# 7						# HDD attrs
		elif (( K > 1 )); then
			unset P ; Part_t P
			LINE=${LINE%\;}			# trailer aka EOL weg
			LINE=${LINE%:*}	
			P.attrs=${.sh.match:1}	# part attrs like bios_grub
			LINE=${LINE%:*}
			P.name=${.sh.match:1}	# part name like primary or zfs
			LINE=${LINE%:*}
			P.type=${.sh.match:1}	# part type, e.g. zfs or linux-swap(v1)
			LINE=${LINE%:*}
			LINE=${LINE//s}			# skip size, remove trailing s
			LINE=${LINE%:*}
			P.end=${.sh.match:1}	# last sector of the part (incl.)
			LINE=${LINE%:*}
			P.start=${.sh.match:1}	# first sector of the part
			P.n=${LINE}				# number of the part
			X=
			P.type=${FS2CODE[${P.type}]}
			[[ -z ${P.type} ]] && P.type='_0000'
			[[ ${P.attrs} =~ msftdata ]] &&			P.type='_0100' 
			[[ ${P.attrs} =~ msftres ]] &&			P.type='_0c01' 
			[[ ${P.attrs} =~ (msftrecv|diag) ]] &&	P.type='_2700'
			[[ ${P.attrs} =~ prep ]] &&				P.type='_4100' 
			[[ ${P.attrs} =~ swap ]] &&				P.type='_8200' 
			[[ ${P.attrs} =~ irst ]] &&				P.type='_8400' 
			[[ ${P.attrs} =~ lvm ]] &&				P.type='_8e00' 
			[[ ${P.attrs} =~ atvrecv ]] &&			P.type='_af04' 
			[[ ${P.attrs} =~ hp-service ]] &&		P.type='_c002' 
			[[ ${P.attrs} =~ boot ]] &&				P.type='_ef00' 
			[[ ${P.attrs} =~ bios_grub ]] &&		P.type='_ef02'
			[[ ${P.attrs} =~ raid ]] &&				P.type='_fd00' 
			[[ ${P.attrs} =~ hidden ]] &&			X+=",62"
			[[ ${P.attrs} =~ read_only ]] &&		X+=",60"
			[[ ${P.attrs} =~ legacy_boot ]] &&		X+=",2"
			P.attrs=${X:1}
			DISK.add P
		fi
		(( K++ ))
	done
}

function getDiskInfoFdisk {
	typeset -n DISK=$1
	typeset L X T V
	integer S=1 I K
	X=${ (( $3 )) && cat "$2" || ${SFDISK} -l -o Device,Start,End,Attrs,Type-UUID,Name "$2" ; }
	DISK.devpath="$2"
	print "$X" | while read -A L ; do
		unset P ; Part_t P
		[[ $L == 'Disk' && ${L[7]} == 'sectors' ]] && \
			DISK.devpath="${L[1]%:}" && DISK.sz=${L[6]} && continue
		[[ $L == 'Sector' && ${L[1]} == 'size' ]] && \
			DISK.lssz=${L[3]} && DISK.pssz=${L[6]} && continue
		[[ $L == 'Disklabel' ]] && DISK.ptt=${L[3]} && continue
		[[  $L == 'Device' ]] && S=0 && continue
		(( S )) && continue
		P.n=${L##*[^0-9]}
		P.start=${L[1]}
		P.end=${L[2]}
		I=3
		X=
		for (( I=3; I <  ${#L[@]}; I++ )); do
			if (( ${#L[3]} == 36 )) && [[ ${L[3]:8:1} == '-' ]]; then
				V=
				[[ -n $X ]] && for T in $X ; do
					if [[ $T == 'RequiredPartiton' ]]; then
						V+=',0'
					elif [[ $T == 'NoBlockIOProtocol' ]]; then
						V+=',1'
					elif [[ $T == 'LegacyBIOSBootable' ]]; then
						V+=',2'
					elif [[ ${T:0:5} == 'GUID:' ]]; then
						V+=",${T:5}"
					fi
				done
				P.attrs="${V:1}"
				P.type=${GUID2CODE[${L[I]}]}
				(( I++ ))
				break
			fi
			X+=" ${L[I]}"
		done
		X=
		while (( I < ${#L[@]} )); do
			X+=" ${L[I]}"
			(( I++ ))
		done
		P.name="${X:1}"
		DISK.add P
	done
}

function getDiskInfoSgdisk {
	typeset -n DISK=$1
	typeset L X
	typeset -l l
	integer S=1 I
    X=${ (( $3 )) && cat "$2" || ${SGDISK} -Pp "$2" ; }
	DISK.devpath="$2"
	DISK.ptt=
	print "$X" | while read -A L ; do
		unset P ; Part_t P
		[[ $L == 'Disk' && ${L[3]} == 'sectors,' ]] && \
			DISK.devpath="${L[1]%:}" && DISK.sz=${L[2]} && continue
		[[ $L == 'Logical' ]] && DISK.lssz=${L[3]} && continue
		[[ $L == 'Number' ]] && S=0 && continue
		(( S )) && continue
		P.n=$L
		P.start=${L[1]}
		P.end=${L[2]}
		l=${L[5]}			# convert to lower case
		P.type="_$l"
		X=
		for (( I=6 ; I < ${#L[@]} ; I++ )); do
			X+=" ${L[I]}"
		done
		P.name="${X:1}"
		DISK.add P
	done
	if [[ -n ${FDISK} ]]; then
		${FDISK} -l ${DISK.devpath} | while read -A L ; do
			[[ $L == 'Sector' && ${L[1]} == 'size' ]] && DISK.pssz=${L[6]}
			[[ $L == 'Disklabel' ]] && DISK.ptt=$3
		done
	fi
}

function getDiskInfo {
	if (( $4 )); then
		[[ ! -f $2 ]] && print -u2 "$2 is not a file - skipped" && return 1
	elif [[ ! -b $2 ]]; then
		print -u2 "$2 is not a block device - skipping partition check."
		return 1
	fi
	typeset -n DISK=$1
	typeset X=$2 B
	if [[ $X =~ p[0-9]+$ ]]; then
		X="${X%p*}"
	elif [[ $X =~ /sd[a-z][0-9]$ ]]; then
		X="${X%%*([0-9])}"
	fi
	(( $4 )) || DISK.devalias="$X"
	getDiskInfo$3 DISK "$X" $4 || return $?
	[[ -z ${DISK.devalias} ]] && DISK.devalias=${DISK.devpath}
	return 0
}

# SanDisk Optimus Max (SDLLOCDR038T5CA1): 512/4096 log/phys sector size
# jel+sandisk@cs.uni-magdeburg.de

function compareDisks {
	typeset -n A=$1 B=$2
	[[ -n $3 ]] && typeset -n INFO=$3 || typeset -a INFO
	integer RES=0
	if (( A.sz != B.sz )); then
		INFO+=( "different size (${A.sz} vs. ${B.sz} sectors)" )
		(( RES++ ))
	fi
	if (( A.lssz != B.lssz )); then
		INFO+=( "different logical block size (${A.lssz} vs. ${B.lssz})" )
		(( RES++ ))
	fi
	if (( A.pssz != B.pssz )); then
		INFO+=( "different physical block size (${A.lssz} vs. ${B.lssz})" )
		(( RES++ ))
	fi
	if [[ ${A.ptt} != ${B.ptt} ]]; then
		INFO+=( "different label aka disk partition table type ('${A.ptt}' vs. '${B.ptt}')" )
		(( RES++ ))
	fi
	typeset -A IDXA IDXB
	typeset -a IDXAA IDXBA
	integer I
	for I in ${!A.parts[@]} ; do
		IDXA[$I]=$I
		IDXAA+=( $I )
	done
	for I in ${!B.parts[@]} ; do
		IDXB[$I]=$I
		IDXBA+=( $I )
	done
	if [[ ${#IDXAA[@]} != ${#IDXBA[@]} ]]; then
		INFO+=( "different number of partitions (${#IDXAA[@]} vs. ${#IDXBA[@]})" )
		(( RES++ ))
	fi
	for I in ${IDXBA[@]}; do
		if [[ -z ${IDXA[$I]} ]]; then
			INFO+=( "missing partition $I" )
			(( RES++ ))
		else
			if (( A.parts[I].start != B.parts[I].start )); then
				INFO+=( "partition $I should start at ${B.parts[$I].start}s (current ${A.parts[$I].start})" )
				(( RES++ ))
			fi
			if (( A.parts[I].end != B.parts[I].end )); then
				INFO+=( "partition $I should end at ${B.parts[$I].end}s (current ${A.parts[$I].end})" )
				(( RES++ ))
			fi
			# filesystems are different thing
			# also it doesn't realy matter, whether logical or primary, anyway:
			if [[ ${A.parts[I].type} != ${B.parts[I].type} ]]; then
				INFO+=( "partition $I type should be 0x${B.parts[$I].type:1} (current 0x${A.parts[$I].type:1})" )
				(( RES++ ))
			fi
			if [[ ${A.parts[$I].attrs} != ${B.parts[$I].attrs} ]]; then
				if [[ -z ${B.parts[$I].attrs} ]]; then
					INFO+=( "partition $I should have no attrs set" )
				else
					INFO+=( "partition $I should have the following attrs: ${B.parts[$I].attrs}" )
				fi
				(( RES++ ))
			fi
		fi
	done
	return $RES
}

# Generate and store commands in arg2 to stop all Linux MD Raid devices aka
# /dev/md on the given raw disk device (arg1), to disable related swap, to
# zero out a possible md superblock and finally to remove all its patitions.
function getRmCmds {
	typeset -n D=$1 C=$2	# device path, command array
	integer I
	typeset PRE X F=$3
	PRE=${D.devpath%/*}
	X=${.sh.match:1}
	if [[ $X == 'disk' || $X == 'p0' ]]; then
		PRE+='/p'
	else
		PRE+="/$X"
		if [[ ${X:0:2} == 'sd' ]]; then
			:	# append plain number
		elif [[ ${X:0:4} == 'nvme' || ${X:0:4} == 'disk' \
			|| $X =~ ^[pd][0-9] ]]
		then
			PRE+='p'
		else
			PRE+='p'	# probably
		fi
	fi
	C+=(
		'# Stop all raid devices (sh/bash/ash/dash/ksh* syntax):'
		'for X in /dev/md[0-9]* ; do  mdadm --stop $X 2>/dev/null ; done'
		' '
		'# Destroy data, which might be in the way.'
		'# But first unlock all mounted zfs and destroy a possibly existing'
		'# rpool including its snapshots and deduced clones.'
		'# If there are other pools containing the new boot disks or your'
		'# current root pool has a different name, then you need to umount'
		'# and destroy them in a similar way first. NOTE that zfs utils are'
		'# not POSIXLY correct - if set commands will fail/produce garbage!'
		'unset POSIXLY_CORRECT'
		'/sbin/modprobe zfs' ' '
		'cd /run'
		'zfs umount -a'
		'zpool destroy rpool 2>/dev/null'
	)
	for I in ${!D.parts[@]}; do
		[[ ${D.parts[$I].name} =~ swap ]] && C+=( "swapoff ${D.devpath}" )
		# make sure, that the kernel/md driver doesn't find a md array
		# signature - re-partioning may not overwrite the related place on disk
		C+=( "mdadm --zero-superblock ${PRE}$I 2>/dev/null" )
		# actually only needed for MBR driven disks
		[[ $F == 'p' ]] && C+=( "parted ${D.devpath} rm $I" )
		#[[ $F == 's' ]] && C+=( "sgdisk -d ${D.parts[I].n} ${D.devpath}" )
		[[ $F == 'f' ]] && C+=( "sfdisk --part-type ${D.devpath} ${D.parts[I].n} ${CODE2GUID['_0000']%%:*}" )
	done
	[[ $F == 's' ]] && C+=( "sgdisk --zap-all ${D.devpath}" )
	[[ $F == 'f' ]] && C+=( "printf 'g\\\nw\\\nq\\\n' | fdisk ${D.devpath}" )
	C+=( 'partprobe' )
}

# Generate and store commands in arg2 to re-partion the given raw disk
# device (arg1).
function getFmtCmds {
	typeset -n D=$1 C=$2 FAKE=$4
	integer I OFFSET EFI N
	typeset X F=$3 A
	C+=( '# repartitioning the disk - all data get lost' )
	if [[ $F == 'p' ]]; then
		C+=( "parted ${D.devpath} mklabel ${D.ptt}" )
		for I in ${!D.parts[@]}; do
			A=${D.parts[$I].type}
			[[ $A == '_ef00' ]] && EFI=${D.parts[$I].n}
			[[ -n $A ]] && A=${CODE2PATTR[$A]}
			X="parted -s ${D.devpath} mkpart ${D.parts[$I].name:-primary}"
			[[ $A == 'swap' ]] && X+=' linux-swap' && A=
			C+=( "$X ${D.parts[$I].start}s ${D.parts[$I].end}s")
			[[ -n $A ]] && C+=( "parted -s ${D.devpath} set $I $A on" )
			A=( ${D.parts[$I].attrs//,/ } )
			for X in "${A[@]}"; do
				[[ $X == '2' ]] && \
					C+=( "parted -s ${D.devpath} set $I legacy_boot on" )
				[[ $X == '60' ]] && \
					C+=( "parted -s ${D.devpath} set $I read_only on" )
				[[ $X == '62' ]] && \
					C+=( "parted -s ${D.devpath} set $I hidden on" )
			done
		done
		C+=( 'partprobe' ' ' "parted ${D.devpath} u s p" )
	elif [[ $F == 's' ]]; then
		# assuming a 'sgdisk --zap-all $dev' has been run
		A='sgdisk -a 1 '
		for I in ${!D.parts[@]}; do
			X=${D.parts[$I].n}
			[[ ${D.parts[$I].type} == '_ef00' ]] && EFI=$X
			A+='\\'"\n\t\t"
			A+="-n ${X}:${D.parts[$I].start}:${D.parts[$I].end} "
			A+="-t ${X}:${D.parts[$I].type:1} "
			[[ -n ${D.parts[$I].name} ]] && \
				A+="-c '${X}:${D.parts[$I].name}' "
			if [[ -n ${D.parts[$I].attrs} ]]; then
				for T in ${D.parts[$I].attrs//,/ } ; do
					A+="-A ${D.parts[$I].n}:set:${T} "
				done
			fi
			if (( I == 2 )); then
				X="/var/lib/partman/devices/${D.devpath//\//=}/"
				(( N = ${D.parts[$I].start} * ${D.lssz} ))
				X+="${N}-"
				(( N = (${D.parts[$I].end} + 1) * ${D.lssz} - 1 ))
				X+="$N"
				FAKE+='\t# partman crap is not able to detect /'
				FAKE+="\n\techo zfs >$X/acting_filesystem\n"
				FAKE+="\n\techo zfs >$X/method\n"
				FAKE+="\n\techo / >$X/mountpoint\n"
			fi
		done
		C+=( "$A"'\\'"\n\t\t${D.devpath}" )
		C+=( 'partprobe' ' ' "sgdisk -p ${D.devpath}" )
	elif [[ $F == 'f' ]]; then
		# actually we could do it in one row using a temp file, but that's
		# less transparent to the user
		OFFSET=0			# we assume, part. 1 the first of all
		for I in ${!D.parts[@]}; do
			A="printf '"
			[[ ${D.parts[$I].type} == '_ef00' ]] && EFI=${D.parts[$I].n}
			if (( D.parts[I].n == 1 )) && \
				[[ ${D.parts[$I].type:1:2} == 'ef' ]] && \
				(( D.parts[I].start < 2048 ))
			then
				C+=( '# fdisk does not allow start sector < 2048 - adjusted' )
				(( OFFSET=2048 - D.parts[I].start ))
				(( D.parts[I].start+=OFFSET ))
				(( D.parts[I].end+=OFFSET ))
				(( D.parts[I+1].start+=OFFSET ))
				# if .start > .end it is trash anyway
			fi
			A+='n\\n'
			A+="${D.parts[$I].n}\\\n"
			A+="${D.parts[$I].start}\\\n"
			A+="${D.parts[$I].end}\\\nw\\\nq\\\n' | fdisk ${D.devpath}"
			C+=( "$A >/dev/null" )
			if [[ -n ${D.parts[$I].attrs} ]]; then
				V=
				for T in ${D.parts[$I].attrs//,/ } ; do
					if [[ $T == '0' ]]; then
						V+=',RequiredPartiton'
					elif [[ $T == '1' ]]; then
						V+=',NoBlockIOProtocol'
					elif [[ $T == '2' ]]; then
						V+=',LegacyBIOSBootable'
					else
						V+=",$T"
					fi
				done
				C+=( "sfdisk --part-attrs ${D.devpath} ${D.parts[$I].n} '${V:1}'" )
			fi
		done
		C+=( 'partprobe' )
		C+=( ' ' "fdisk -l ${D.devpath}" )
	fi
	if (( EFI )); then
		getPartPrefix X ${D.devalias}
		C+=( "mkfs.fat $X${EFI}" )
	fi
	if [[ -n ${FAKE} ]]; then
		FAKE+='\n\t# Fix the bogus partman / checker\n'
		FAKE+="\tsed -i -e '/zfs set mountpoint/ s/log/: #log/' /lib/partman/mount.d/70zfs\n"
		FAKE+="\tsed -i -e '/= free/ s/^/#/' /lib/partman/fstab.d/zfs\n"
	fi
}

# Create an rpool from the given Disk (arg1) and store commands into
# vname (arg2).
function getZfsCmds {
	typeset -n D=$1 C=$2
	typeset X ZBASE='rpool/ROOT/linux' S

	integer S M SWAP=$3
	(( S=log2(D.lssz) ))
	(( S < 9 )) && S=9
	(( M=log2(MEMORY) + 11 - 30 ))
	(( M=pow(2, M) ))
	X="-o ashift=$S -m none -o altroot=/target -O canmount=on"
	X+=' \\\n\t\t'"-o cachefile=/boot/zfs/zpool.cache"
	X+=' \\\n\t\t-o autoexpand=off -o autoreplace=off -o failmode=continue'
	X+=' \\\n\t\t-o delegation=on -o listsnapshots=off'
	X+=' \\\n\t\t-O compression=lz4 -O checksum=on -O atime=on -O xattr=sa'
	X+=' \\\n\t\t-O aclinherit=restricted -O acltype=off -O overlay=off'
	X+=' \\\n\t\t-O casesensitivity=mixed -O normalization=none'
	C+=(
		'# We assume, that "rpool" is the boot disk pool and mount it temp. to'
		'# /target , so that installation can continue.'
		'unset POSIXLY_CORRECT'
		'mkdir -p /boot/zfs'
		"zpool create -f $X "'\\\n\t\t'"rpool ${D.zfs}"
		'partprobe'			# just in case zpool did stupid things
		"zfs create -o mountpoint=none ${ZBASE%/*}"
		"zfs create -o canmount=noauto -o mountpoint=/ ${ZBASE}"
		"zpool set bootfs=${ZBASE} rpool"
		# set mountpoint to legacy, so that partman fuck automtically mounts
		# it before it starts the real install (it doesn't understand zfs).
		"zfs create -o canmount=noauto -o mountpoint=legacy ${ZBASE}/var"
		# nothing gets installed here, so sufficient to mount on boot
		'zfs create -o canmount=on -o mountpoint=/var/share rpool/VARSHARE'
		# kernel dump area
		"zfs create -b 1M -V ${M}g -o compression=off rpool/dump"
	)
	if (( SWAP )); then
		PATH=/opt/ast/bin:$PATH		# busybox/netimage has no getconf
		S=${ getconf PAGESIZE; }
		[[ -z $S ]] && S=4096
		C+=( "zfs create -V ${SWAP}g -b $S -o logbias=throughput -o sync=always -o primarycache=metadata -o secondarycache=none -o com.sun:auto-snapshot=false -o compression=off rpool/swap" )
	fi
	# if 'zpool create' applied to a wrong partition, this may show the error
	C+=( ' ' '# check' )
	if [[ -n ${SGDISK} ]]; then
		C+=( "sgdisk -p ${D.devpath}" )
	elif [[ -n ${SFDISK} ]]; then
		C+=( "fdisk -l ${D.devpath}" )
	elif [[ -n ${PARTED} ]]; then
		C+=( "parted ${D.devpath} u s m" )
	fi
	C+=( 'zfs list -r rpool' )
}

function init {
	[[ ${ uname -s ; } == 'SunOS' ]] && return

	X="======= partman ======\n${ set ; }"
	X+="\n/proc/mdstat:\n${ cat /proc/mdstat ; }"
	X+='\n======================'
	#print "$X"
	# fix the braindamaged original
	[ -x /etc/auto-raidcfg ] && cp /etc/auto-raidcfg /bin/auto-raidcfg

	# this doesn't work because of the exec braindamage
	# . /usr/share/debconf/confmodule
	# instead we need to create a separate busybox script, when we need to use
	# db_* stuff ...
}

function getProductName {
	typeset -n PRODUCT=$1
	typeset SMBIOS=${ whence dmidecode ; }
	[[ -z ${SMBIOS} ]] && SMBIOS=${ whence smbios ; }
	
	${SMBIOS} -t 1 2>/dev/null | while read LINE ; do
		[[ ${LINE:0:13} == 'Product Name:' ]] || continue
		PRODUCT="${LINE:14}"
		break
	done
}

function getSwap {
	typeset -n N=$1
	print \
'\nTo calculate a proper disk layout the swap space to be used is needed.
Usually RAM/4 GB (our default) are sufficient. However, if this is a laptop
and you wanna use hibernation, you might need 1.5xGB of RAM of your machine.
You may adjust this via CLI option "-s GB".\n'
	typeset X=4 K V T
	integer D P=0
	# this is alway < 2^n , so no need to -1
	(( D=rint(log2(MEMORY)) - 22 ))
	(( D < 0 )) && D=0
	(( D=pow(2, D) ))
	N=$D
	while (( N < 0 )) ; do
		P=1
		X=$D
		read X?"Enter the swap size in GB (default: $D): "
		[[ $X =~ ^[0-9]+$ ]] && N=$X
	done
	(( N < 0 )) && N=$D
	(( P )) && print
}

function getMountTarget {
	(( USE_ZFS )) || return 0

	typeset X=
	[[ -e /etc/default-release ]] && X=$(</etc/default-release)
	[[ $X == 'bionic' ]] && return 0
	# required since focal
	typeset -n S=$1
	S+='
# [u]mount relevant parts to /target to make partman happy for install
/sbin/modprobe zfs
/bin/umount /target/boot/efi 2>/dev/null
/bin/umount /target/var/share 2>/dev/null
/bin/umount /target/var 2>/dev/null
rm -rf /target/var /target/boot
/sbin/zfs mount rpool/ROOT/linux
mkdir /target/var
/bin/mount -t zfs rpool/ROOT/linux/var /target/var
/sbin/zfs mount rpool/VARSHARE
'
	(( USE_EFI )) && S+='\nmkdir -p /target/boot/efi'
}

function doMain {
	typeset D X
	typeset -l A
	integer RES=0 SWAP=0 USE_FILES=1 I N
	PARTED=${ whence parted ; }
	SGDISK=${ whence sgdisk ; }
	SFDISK=${ whence fdisk ; }

	if [[ -z ${FILES} ]]; then
		if [[ -n ${SFDISK} ]]; then
			FILES='Fdisk'
		elif [[ -n ${SGDISK} ]]; then
			FILES='Sgdisk'
		elif [[ -n ${PARTED} ]]; then
			FILES='Parted'
		else
			print -u2 'ERROR: No suitable partition tool found.' \
				'Make sure fdisk is installed, e.g.\n\tby adding it to your' \
				'your preseed file like this:' \
				'\n\t"d-i anna/choose_modules string util-linux-udeb"'
			return 99
		fi
		USE_FILES=0
	elif [[ ${FILES} == 'p' ]]; then
		print -u2 'Warning: parted is ancient junk. Output contains not all' \
			'required information\n\tto properly create a GPT partition.'
		FILES='Parted'
	elif [[ ${FILES} == 's' ]]; then
		print -u2 'Warning: sgdisk output might not contain all required' \
			'information and\n\t supports GPT labeled disks, only.'
		FILES='Sgdisk'
	elif [[ ${FILES} == 'f' ]]; then
		FILES='Fdisk'
	else
		print -u2 "ERROR: Unsupported part output format '${FILES}' - exiting"
		return 98
	fi
	[[ -z ${FORMAT} ]] && FORMAT='s'
	if [[ ${FORMAT} == 's' || ${FORMAT} == 'sgdisk' ]]; then
		FORMAT='s'
	elif [[ ${FORMAT} == 'f' || ${FORMAT} == 'fdisk' ]]; then
		FORMAT='f'
	elif [[ ${FORMAT} == 'p' || ${FORMAT} == 'parted' ]]; then
		print -u2 '\nWarning: ancient formatter 'parted' should not be used.' \
			'It is unsuitable\n\tfor GPT formatting.\n'
		FORMAT='p'
	else
		print -u2 "ERROR: Unknown disk formatter tool '${FORMAT}' - exiting."
		return 97
	fi

	# We put it here, since when pre.sh gets executed, neither the full kernel
	# is expanded yet nor are the related devices available.
	#/sbin/udevadm settle
	if (( USE_ZFS == -1 )); then
		X=
		if [[ ${ uname -s; } == 'SunOS' ]]; then
			X=1
		else
			X=${ /bin/lsmod | grep zfs; }
			[[ -z $X ]] && \
				{ /sbin/modprobe zfs ; X=${ /bin/lsmod | grep zfs; } ; }
		fi
		[[ -n $X ]] && USE_ZFS=1 || USE_ZFS=0
	fi
	if (( MEMORY < 1 )); then
		if [[ -e /proc/meminfo ]]; then
			while read K V T ; do
				[[ $K == 'MemTotal:' ]] && MEMORY=$V && break
			done </proc/meminfo
		elif [[ ${ uname -s; } == 'SunOS' ]]; then
			prtconf | while read -A L ; do
				[[ $L == 'Memory' ]] && (( MEMORY=L[2]*1024 )) && break
			done
		else
			MEMORY=4096		# assume 4GB
		fi
	fi
	
	if [[ -x /lib/udev/bayLinks.sh ]]; then
		X=/tmp/baylist.out
		[[ -f /lib/udev/baymap ]] || /lib/udev/bayLinks.sh -m >$X 2>&1
		# make sure, we have the chassis links
		/lib/udev/bayLinks.sh -u >>$X 2>&1
	else
		print -u2 'Warning: /lib/udev/bayLinks.sh not available!\n' \
			'\t/dev/chassis/*/HDD* symlinks might not be uptodate.'
	fi
	if [[ -x /lib/udev/bayLinksNvme.sh ]]; then
		X=/tmp/baylistNvme.out
		[[ -f /lib/udev/baymapNvme ]] || /lib/udev/bayLinksNvme.sh -m >$X 2>&1
		# make sure, we have the chassis links
		/lib/udev/bayLinksNvme.sh -u >>$X 2>&1
	else
		print -u2 'Warning: /lib/udev/bayLinksNvme.sh not available!\n' \
			'\t/dev/chassis/*/SSD* symlinks might not be uptodate.'
	fi

	if [[ -z $1 ]]; then
		print -u2 'Nothing to do - params missing?'
		showUsage
		return 0
	fi

	. init

	typeset -i GB_SWAP=0
	(( FIX_SWAP >= 0 )) && GB_SWAP=${FIX_SWAP} || getSwap GB_SWAP

	typeset SCRIPT
	I=1 N=0
	for D in "$@" ; do
		unset DISK RDISK
		Disk_t DISK RDISK
		getDiskInfo DISK "$D" ${FILES} ${USE_FILES} || continue
		if ! DISK.getRecommendedLayout RDISK ${GB_SWAP} ${USE_ZFS} ; then
			print -u2 "\nERROR: no recommended layout for ${DISK.devpath} -" \
				'not enough space.'
			(( ! USE_ZFS )) && print '\tYou may limit space required for swap'\
				'via option "-m GB".\n'
			continue
		fi
		unset INFO PFAKE; typeset -a INFO PFAKE
		SCRIPT='\n'
		if ! compareDisks DISK RDISK INFO ; then
			print -u2 \
				"Problems found wrt. recommended layout of ${DISK.devpath}:"
			for X in "${INFO[@]}"; do
				print -u2  "\t- $X"
			done
			print -u2 '\nTo reformat the disk, use e.g. the following commands:'
			unset INFO; typeset -a INFO
			getRmCmds DISK INFO ${FORMAT}
			for X in "${INFO[@]}"; do
				SCRIPT+="\t$X\n"
			done
			SCRIPT+='\n'
			unset INFO; typeset -a INFO
			getFmtCmds RDISK INFO ${FORMAT} PFAKE
			for X in "${INFO[@]}"; do
				SCRIPT+="\t$X\n"
			done
			if (( USE_ZFS && I )); then
				[[ -n ${PFAKE} ]] && SCRIPT+="\n${PFAKE}"
				SCRIPT+='\n'
				unset INFO; typeset -a INFO
				getZfsCmds RDISK INFO ${GB_SWAP}
				for X in "${INFO[@]}"; do
					SCRIPT+="\t$X\n"
				done
				(( I-- ))
			fi
			(( ! N )) && getMountTarget SCRIPT && (( N++ ))
			print -u2 "${SCRIPT}"
			X="/tmp/format-disk${RES}.sh"
			print "#!/bin/sh\n${SCRIPT}" >"$X"
			chmod 755 "$X"
			A=
			if (( ! USE_FILES )); then
				while [[ $A != 'y' && $A != 'n' ]]; do
					A='y'
					read -v A?"Should $D be reformatted NOW as shown above: "
				done
				if [[ $A == 'y' ]]; then
					A=
					while [[ $A != 'y' && $A != 'n' ]]; do
						A='y'
						read -v A?"Please confirm: ALL DATA on $D will be LOST: "
					done
					if [[ $A == 'y' ]]; then
						"$X"
						unset DISK RDISK ; Disk_t DISK RDISK
						getDiskInfo DISK "$D" ${FILES} ${USE_FILES}
						DISK.getRecommendedLayout RDISK ${GB_SWAP} ${USE_ZFS}
						compareDisks DISK RDISK && (( RES-- ))
					fi
				fi
			fi
			if (( USE_FILES )) || [[ $A != 'y' ]]; then
				print -u2 "Formatting script saved as '$X'."
			fi
			(( RES++ ))
		fi
	done
	return ${RES}
}

# partman invocation order:
#	/lib/partman/init.d/	must return 0 (also starts parted_server if needed)
#		/lib/partman/update.d/	invoked for each new device/part found
#	/lib/partman/display.d/	0 = continue, 1-99 restart, 100-254 skip, 255 abort
#	/lib/partman/check.d/	return reasonable code
#	/lib/partman/undo.d/	if undo invoked by user. must return 0.
#	/lib/partman/commit.d/	return 0 or stop; jumps to init.d/ if part continues
#	/lib/partman/finish.d/	return 0 or 1 (stops parted_server), >1 abort
#
# see also: /var/lib/partman/devices/

USAGE="[-?${VERSION}"' ]
[-copyright?Copyright (c) 2015 Jens Elkner. All rights reserved.]
[-license?CDDL 1.0]
[+NAME?'"${PROG}"' - re-partitioning script generator.]
[+DESCRRIPTION?Usually we use this script as debian installer \bpartman/early_command\b, which means that it gets executed as the first of all partman scripts (see /lib/partman/init.d/01early_command). It generates a single script for each given \afile_or_dev\a, which contains the commands to re-partion it to match the our defaults/recommandations. The user will be asked to executed it, when generated. If not, it gets stored in an appropriate location (for later use/inspection). This way it is used to overcome all this partman limitations/ancient stuff by formatting drives with the proper tools, so that partman sees all what it wants to see and does not any crazy re-formatting by itself and debian installer can continue without intervention to the next task.]
[+?However, if the re-partitoning script gets fired, an "undo" option as partman usually provides in interactive installs is not supported (because early command gets invoked before partman reads the current layout).]
[h:help?Print this help and exit.]
[F:functions?Print a list of all functions available.]
[T:trace]:[functionList?A comma separated list of functions of this script to trace (convinience for troubleshooting).] 
[+?]
[e:efi?Create an EFI system partion instead of a legacy bios/grub partition.]
[f:files]:[type?Mark the given operands (\afile_or_dev\a) as files containing the output of "parted -m \asd_device\a u s p" for \atype\a=\bp\b, "fdisk -l -o Device,Start,End,Attrs,Type-UUID,Name \asd_device\a" for \atype\a=\bf\b - preferred - (requires fdisk from util-linux 2.27.1+) or "sgdisk -Pp \asd_device\a" for \atype\a=\bs\b. This is usually used for offline development/testing of this script.]
[l:list-guid?List all known partition GUIDs incl. sgdisk code and exit.]
[m:memory]:[num?Assume \anum\a GiB of physical RAM. If not given, /proc/meminfo or prtconf gets consulted to find out the value automatically.]
[N:noboot?Do not reserve a 256 MiB Grub/BIOS boot partition (EF02) and for non-ZFS systems also skip generating an entry for a swap partition (for zfs system swap gets reserved as part of the rpool, i.e. rpool/swap volume if needed). This is ok, if the related disks will be used as normal data pool disk. Never use this option for [potential]] boot disks! For ZFS hot spares it is not that important, because these disks get reformatted when needed, anyway.]
[s:swap]:[num?Use \anum\a GiB for swap and do not deduce it from available RAM.]
[t:tool]:[formatter?The disk partition tool to use. Choices are: \bp\b for \bparted\b (the worst choice since it is actually an MBR oriented tool and is not really usable for GPT formatted disks). \bf\b stands for \bfdisk\b from linux-utils and finally the default/preferred \bs\b for \bsgdisk\b.]
[Z:nozfs?Do not use ZFS, i.e. assume that Linux md will be used.]
[z:zfs?Force to use ZFS, even if no zfs kernel modules are loaded yet.]
[+EXAMPLES]{
	[+?'"${PROG}"' /dev/sda /dev/sdb]
}
[+EXIT STATUS?The script usually exits with:]{
	[+0?The script did nothing, move to the next script in \bdisplay.d/\b.]
	[+1..99?Restart the partitioning, i.e. move again to the first script in \bdisplay.d/\b.]
	[+100..254?The script succesfuly partitioned the disks, do not execute the following scripts in \bdisplay.d/\b.]
	[+255?Abort the partitioning.]
}
\n\n\afile_or_dev\a ...
'
integer BOOT=1 USE_ZFS=-1 MEMORY=0 LIST_GUID=0 FIX_SWAP=-1 USE_EFI=0
typeset PARTED= SGDISK= SFDISK= FILES= FORMAT=

X="${ print ${USAGE} ; }"
while getopts "$X" OPT ; do
	case "${OPT}" in
		f) FILES=${OPTARG} ;;
		N) BOOT=0 ;;
		h) showUsage; exit 1 ;;
		T)	if [[ ${OPTARG} == 'ALL' ]]; then
				typeset -ft ${ typeset +f ; }
			else
				typeset -ft ${OPTARG//,/ }
			fi
			;;
		F) typeset +f ; exit 0 ;;
		t) FORMAT=${OPTARG} ;;
		Z) USE_ZFS=0 ;;
		z) USE_ZFS=1 ;;
		m) MEMORY=${OPTARG} || exit 1 ;;
		l) LIST_GUID=1 ;;
		s) [[ ${OPTARG} =~ ^[0-9]+$ ]] && FIX_SWAP=${OPTARG} || \
				{ print -u2 'Invalid swap size value' && exit 255 ; } ;;
		e) USE_EFI=1 ;;
		*) print -u2 "Unknown option '-${OPT}'."; showUsage 1 ;;
	esac
done
X=$((OPTIND-1))
shift $X && OPTIND=1
unset X

(( LIST_GUID )) && listGUIDs && exit 0

[[ -z $1 ]] && showUsage 1 && exit 2
doMain "$@"
