#!/usr/bin/perl
#
#-------------------------------------------------------------------------------
# File Name: "sparted.pl" - Script Generator for parted (sparted is not spurted)
#
# Copyright: Public Domain Software (NOT TO BE SHAREWARE OR FREEWARE)
#
# Version (??: secondary series, can be revised by somebodies)
# *.*?? by ***	May be revised for something
#
# Version (ps: primary series, reserved for S. )
# 1.0ps by S.	Created to support Android-x86.
#
# Related Softwares (required):
#
# - Download & Install Perl | ActiveState (freeware, 64bit)
#   https://www.activestate.com/products/perl/downloads/
#   https://cli-msi.s3.amazonaws.com/ActivePerl-5.28.msi
#   SHA256: "3e50e786852c00e011dfab29133fe95cf86854b9ed057b96e32f8fc24920b85c"
#   SHA256 may not be checked by default because the file is altered frequently.
#   Used: installed "ActivePerl-5.28.msi"
#-------------------------------------------------------------------------------

if ($ARGV[0] eq '-h') {
	$^X =~ /([\\\/])/;
	$_ = $1 ? ".$1" : '';
	$0 =~ /([^\\\/]+)$/;
	print <<EOD;
Usage: [perl] [$_]$1 [MSC [NPU [OSD[:NAM] [MBR [MPN [MPS [WRP]]]]]]]
 -    Use as default value
 {}   Used as default value (See below)
 MSC  Max. Storage Capacity [GB] (e.g. 2 .. 64,{128},256,512,1024 .. 4194304)
 NPU  Normalized Partition Unit [GiB] (1 .. {16},32,64,128,256,512 .. < MSC)
 OSD  OS/Data, {W[indows]}: Windows, L[inux]: Linux (case-insensitive)
 NAM  Partition Name / Volume Label (Max. 6 characters, {WIN110}/{LIN000})
 MBR  Partition Table Type, {g[pt]}: GPT, m[sdos]: MBR (case-insensitive)
 MPN  GPT mode: MSR (Microsoft Reserved) Partition No. ({2},3,4)
 MPS  GPT mode: MSR (Microsoft Reserved) Partition Size [MiB] ({16},32,64,128)
      MSR Partition Size is limited to 16[MiB] if MSR Partition No. == 2
 WRP  Output Wrapper (0: Core, 1: Scenario Text *.txt, {2}: Shell Script *.sh)

e.g. $_$1 2000 128 W:WIN110 g 2 16
e.g. $_$1 2000 128 W:WIN070 g 4 128
e.g. $_$1 64 - L:LIN000 m
EOD
	exit 0;
}

{
	my @arg = split ':', $ARGV[2];	# OSD[:NAM]
	$arg[1] = '' if $arg[0] eq '';

	@OSD = (Windows, Linux);
	$OSD = 0;		# OS/Data
	{
		(my $arg) = grep {$OSD[$_] =~ /$arg[0]/i} 0..$#OSD if $arg[0] ne '' && $arg[0] ne '-';
		$OSD = $arg if $arg ne '';
	}

	@MPN = (2..4);		# Partition No. 2 .. 4 (4: Each Partition No. will be same as MBR mode)
	$MPN = 0;		# GPT mode: MSR (Microsoft Reserved) Partition No.
	($MPN) = grep {$MPN[$_] == $ARGV[4]} 0..$#MPN if $ARGV[4] >= 2 && $ARGV[4] <= 4;

	@MPS = (4..7);		# 2**4 = 16[MiB] .. 2**7 = 128[MiB]
	$MPS = 0;		# [MiB] GPT mode: MSR (Microsoft Reserved) Partition Size 2**N (exponent of 2)
	$ARGV[5] = int log($ARGV[5])/log(2)+0.9999999 if $ARGV[5] && $ARGV[5] =~ /^\d+$/;	# septuple 9 required to distinguish 2**22 or less
	($MPS) = grep {$MPS[$_] == $ARGV[5]} 0..$#MPS if $ARGV[5] >= 4 && $ARGV[5] <= 7;
	$MPS = 0 if !$MPN;	# MSR Partition Size is limited to 16[MiB] if MSR Partition No. == 2

	@MBR = (gpt, msdos);	# GPT (GUID Partition Table), MBR (Master Boot Record)
	$MBR = 0;		# Partition Table Type
	{
		(my $arg) = grep {$MBR[$_] =~ /$ARGV[3]/i} 0..$#MBR if $ARGV[3] ne '' && $ARGV[3] ne '-';
		$MBR = $arg if $arg ne '';
	}

# partitioning - Partition alignment: parted shows warning - Ask Ubuntu
# https://askubuntu.com/questions/701729/partition-alignment-parted-shows-warning
# Note that also the last 33 sectors of the disk are reserved to keep a copy of the primary GPT-header and the partition table entries.

	@LSR = (33, 0);		# the last sectors of storage reserved to back up GPT/MBR
	$LSR[1] = $LSR[0];	# Align MBR partitions to GPT partitions until converting MBR to GPT

	@ALT = ('m', o, n);	# Alignment Type, m: minimal, o: optimal, n: none, c: cylinder
	$ALT[1] = $ALT[0];	# Align MBR partitions to GPT partitions until converting MBR to GPT

# parted(8) - Linux manual page
# https://man7.org/linux/man-pages/man8/parted.8.html
# mkpart [part-type name fs-type] start end

	@PRT = ('', primary, extended, logical);	# Partition Type

# Partition Name / Volume Label Prefix, Max. GPT Partitions = MBR Offset = 128
	@PRE = ('', (grep s/^/G/, 0..9, A..Z), (grep s/^/H/, 0..9, A..Z), (grep s/^/I/, 0..9, A..Z), (grep s/^/J/, 0..9, A..J));
	push @PRE, (grep s/^/B/, 0..3), E0, (grep s/^/L/, 0..9, A..Z), (grep s/^/M/, 0..9, A..Z), (grep s/^/N/, 0..9, A..Z), (grep s/^/O/, 0..9, A..F);
#	print STDERR join(',', @PRE), "\n";	# for debug only

	@NAM = ('', ESP000, MSR000, REC000, WIN110, LIN000);	# Partition Name / Volume Label (Max. 6 characters)
	$NAM[$OSD+4] = substr $arg[1], 0, 6 if $arg[1] ne '' && $arg[1] ne '-';

	@FST = ('', fat32, ntfs, ext4);	# File System Type

	$PNO = 1;		# Partition No. (to start)

#	$KiB = 2**(10*1-9);	# [sct/KiB] (KiB: Kibibyte, Kilo binary byte)
	$MiB = 2**(10*2-9);	# [sct/MiB] (MiB: Mebibyte, Mega binary byte)
	$GiB = 2**(10*3-9);	# [sct/GiB] (GiB: Gibibyte, Giga binary byte)
	$GB  = 10**9/2**9;	# [sct/GB] (GB: Gigabyte)
	$TiB = 2**(10*4-9);	# [sct/TiB] (TiB: Tebibyte, Tera binary byte)
#	$PiB = 2**(10*5-9);	# [sct/PiB] (PiB: Pebibyte, Peta binary byte)

	$GAP = 2**7*$MiB;	# [sct] Configured Gap Size 128MiB: GPT (1MiB) + ESP (111MiB >= 100MiB) + MSR (16MiB min.)

	@FRM = ();		# Values for Output Format

#	$MNB = 2**7*$GiB;	# [sct] Max. Normalized Partition Boundary (4PiB max.)
	$MSC = 2**7;		# [GB] Max. Storage Capacity (4PiB max.)
	$MSC = $ARGV[0] if $ARGV[0] && $ARGV[0] =~ /^\d+$/;
	push @FRM, $MSC, $MSC*$GiB;
	$MSC *= $GB;		# [sct] Max. Storage Capacity (4PiB max.)

	$NPU = 2**4;		# [GiB] Normalized Partition Unit
	$NPU = 2**(int log($ARGV[1])/log(2)+0.9999999) if $ARGV[1] && $ARGV[1] =~ /^\d+$/;	# septuple 9 required to distinguish 2**22 or less
	push @FRM, $NPU;
	$NPU *= $GiB;		# [sct] Normalized Partition Unit

	$CNB = 0;		# [sct] current Normalized Partition Border (to start)
	$NNB = $CNB+$NPU;	# [sct] next Normalized Partition Border

	$WRP = 2;		# Output Wrapper (0: Core, 1: Scenario Text *.txt, 2: Shell Script *.sh)
	$WRP = $ARGV[6] if $ARGV[6] ne '' && $ARGV[6] >= 0 && $ARGV[6] <= 2;

	$OB3 = '';		# Delayed Operation Output Buffer 3
	$OB2 = '';		# Delayed Operation Output Buffer 2
	$OB1 = '';		# Delayed Operation Output Buffer 1
	$OE1 = 0;		# Delayed Operation Output Enable 1
	$OE1 = 1 if $WRP;

# Reconstructed Arguments
	printf STDERR "%uGB(%ds)<%uGiB(%ds) %uGiB(%ds) %s:%s %s %u %u %u\n", $FRM[0], $MSC, $FRM[0], $FRM[1], $FRM[2], $NPU, $OSD[$OSD], $NAM[$OSD+4], $MBR[$MBR], $MPN[$MPN], 2**$MPS[$MPS], $WRP;

	if ($WRP) {
		$FNB = sprintf "n%uG%uG%s%s", $FRM[0], $FRM[2], substr($OSD[$OSD], 0, 1), substr($MBR[$MBR], 0, 1);	# File Name Base (MBR)
		$FNB .= sprintf "%u_%u", $MPN[$MPN], 2**$MPS[$MPS] if !$MBR;	# File Name Base (GPT)
		$FNX = $WRP == 2 ? '.sh' : '.txt';	# File Name Extension

		open STDOUT, '>', "$FNB$FNX" or die "Can't open \"$FNB$FNX\" : $!";	# redirect STDOUT to a file
		binmode STDOUT;	# ASCII/LF (\n)
		print STDERR "Check \"$FNB$FNX\"\n";
	}
}

if ($WRP >= 2) {
	print <<EOW2;
#!/usr/bin/sh

if [ "\$1" = "" -o "\$1" = "-h" -o "\$1" = "--help" ]; then cat << EOD
Usage: ./$FNB.sh sdX [-r]
 sdX  The device to be selected (e.g. sdc)
 -r   Retry to make the last small partition (interactively)
EOD
exit 0; fi
sdX=\$1

main_$FNB () {
cat << EOD > $FNB.txt
EOW2
}

if ($WRP >= 1) {
	print <<EOW1;
help " Normalized $FRM[0]GB storage, $FRM[2]GiB partitions for $OSD[$OSD] ($MBR[$MBR])
help " Notes: Text to make 2048[sct] aligned, 2**N[GiB] normalized partitions
help " Configured Gap Size 128MiB: GPT (1MiB) + ESP (111MiB) + MSR (16MiB min.)
help " ESP for 512[B/sct] (not for 4096[B/sct]), assuming less writing to ESP
help " Usage: # parted -a $ALT[$MBR] < $FNB.txt
unit s
help " Don't forget to check and change the device to be selected.
select /dev/\${sdX}
help " Don't forget to make the partition table before making partitions.
help "mktable $MBR[$MBR] y
EOW1
}

while ($CNB+$GAP < $MSC) {
	;    if ($PNO == 1) {
		$PRT = 1; $PRE = 1; $NAM = 1; $FST = 1; $PNO = 1;
		$VOL = $NAM;	# Volume Name/Label (ESP -> OS/Data -> GPT mode: Recovery Partition)
#		printf "%3u %13ds ", $PNO, $MSC;	# for debug only
#		printf "mkpart %-9s%-9s%-6s%13ds %13ds\n", $PRT[$PRT], "$PRE[$MBR*128+$PRE]$NAM[$NAM]", $FST[$FST], 1*$MiB, (128-16)*$MiB-1;	# ESP
		printf "mkpart %-9s%-6s%13ds %13ds\n", $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[$NAM]", $FST[$FST], 1*$MiB, (128-16)*$MiB-1;	# ESP
		$OB1 .= "help \" GPT: Set protective MBR boot flag to on if booting in legacy BIOS mode.\n" if !$MBR;
		$OB1 .= "help \"disk_set pmbr_boot on\n" if !$MBR;
		$OB1 .= sprintf "set %3u esp on\n", $PNO;
		$OB1 .= sprintf "set %3u boot on\n", $PNO;
		$OB1 .= sprintf "set %3u lba off\n", $PNO if $MBR;
		$OB1 .= sprintf "help \"set %3u boot on\n", $PNO+1 if $MBR;
		mkfs(\$OB2);	# make File System (format)
		$PRT += 0; $PRE += 1; $NAM += $OSD+3; $FST += $OSD+1; $PNO += 1;
		$VOL = $NAM;	# Volume Name/Label (ESP -> OS/Data -> GPT mode: Recovery Partition)
	} elsif ($PNO == 4 && $MBR && $NNB+$GAP < $MSC) {
		$PRT += 1; $PRE += 1; $NAM += 0; $FST += 0; $PNO += 0;
#		printf "%3u %13ds ", $PNO, $MSC;	# for debug only
#		printf "mkpart %-9s%-9s%-6s%13ds %13ds\n", $PRT[$PRT], "$PRE[$MBR*128+$PRE]$NAM[$NAM]", $FST[0], $CNB, -1;	# MBR mode: Extended Partition
		printf "mkpart %-9s%-6s%13ds %13ds\n", $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[$NAM]", $FST[0], $CNB, -1;	# MBR mode: Extended Partition
		$OB1 .= sprintf "set %3u lba on\n", $PNO if $MBR;
		$PRT += 1; $PRE += 1; $NAM += 0; $FST += 0; $PNO += 1;
	}
	if ($PNO == $MPN[$MPN] && !$MBR) {
#		printf "%3u %13ds ", $PNO, $MSC;	# for debug only
#		printf "mkpart %-9s%-9s%-6s%13ds %13ds\n", $PRT[$PRT], "$PRE[$MBR*128+$PRE]$NAM[2]", $FST[0], $CNB+$GAP-2**$MPS[$MPS]*$MiB, $CNB+$GAP-1;	# GPT mode: MSR
		printf "mkpart %-9s%-6s%13ds %13ds\n", $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[2]", $FST[0], $CNB+$GAP-2**$MPS[$MPS]*$MiB, $CNB+$GAP-1;	# GPT mode: MSR
		$OB1 .= sprintf "set %3u msftres on\n", $PNO if !$MBR;
		$PRT += 0; $PRE += 1; $NAM += 0; $FST += 0; $PNO += 1;
	}
	unless ($NNB-1 < $MSC) {
		$VOL = 3 if !$MBR;	# Volume Name/Label (ESP -> OS/Data -> GPT mode: Recovery Partition)
#		print 'help "';	# comment out the next line
#		printf "%3u %13ds ", $PNO, $MSC;	# for debug only
#		printf "mkpart %-9s%-9s%-6s%13ds %13ds\n", $PRT[$PRT], "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $FST[$FST], $CNB+$GAP, -$LSR[$MBR]-1;	# OS/Data -> GPT mode: Recovery Partition
		printf "mkpart %-9s%-6s%13ds %13ds\n", $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $FST[$FST], $CNB+$GAP, -$LSR[$MBR]-1;	# OS/Data -> GPT mode: Recovery Partition
		print 'help "';	# comment out the next line
#		printf "%3u %13ds ", $PNO, $MSC;	# for debug only
#		printf "mkpart %-9s%-9s%-6s%13ds %13d%%\n", $PRT[$PRT], "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $FST[$FST], $CNB+$GAP, 100;	# OS/Data -> GPT mode: Recovery Partition
		printf "mkpart %-9s%-6s%13ds %13d%%\n", $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $FST[$FST], $CNB+$GAP, 100;	# OS/Data -> GPT mode: Recovery Partition
		$OB3 .= "# Retry to make the last small partition (interactively) when it is not made.\n";
		$OB3 .= "parted -s /dev/\${sdX} rm $PNO\n";
		$OB3 .= sprintf "parted -a %s -- /dev/\${sdX} mkpart %s %s %ds %ds\n", $ALT[$MBR], $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $FST[$FST], $CNB+$GAP, -$LSR[$MBR]-1;
		$OB3 .= sprintf "#parted -a %s -- /dev/\${sdX} mkpart %s %s %ds %d%%\n", $ALT[$MBR], $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $FST[$FST], $CNB+$GAP, 100;
		$OB3 .= "parted -s /dev/\${sdX} set $PNO lba off\n" if $MBR;
		$OB3 .= "# If the mkfs fails try to rerun, or sleep for more seconds before the mkfs.\n";
		$OB3 .= "sleep 1 # [s]\n";
		mkfs(\$OB3);	# make File System (format)
		$OB3 .= "parted -s /dev/\${sdX} unit MiB print unit s print\n";
		print 'help "';	# comment out the next line
	}
#	printf "%3u %13ds ", $PNO, $MSC;	# for debug only
#	printf "mkpart %-9s%-9s%-6s%13ds %13ds\n", $PRT[$PRT], "$PRE[$MBR*128+$PRE]$NAM[$NAM]", $FST[$FST], $CNB+$GAP, $NNB-1;	# OS/Data
	printf "mkpart %-9s%-6s%13ds %13ds\n", $MBR ? $PRT[$PRT] : "$PRE[$MBR*128+$PRE]$NAM[$NAM]", $FST[$FST], $CNB+$GAP, $NNB-1;	# OS/Data
	$OB1 .= sprintf "set %3u lba off\n", $PNO if $MBR;
	mkfs(\$OB2);	# make File System (format)
	$PRT += 0; $PRE += 1; $NAM += 0; $FST += 0; $PNO += 1;
	$CNB = $NNB;
	$NNB += $NPU;
}
$OB2 .= "parted -s /dev/\${sdX} unit MiB print unit s print\n";

sub mkfs {
	my $OBN = $_[0];	# Delayed Operation Output Buffer N (reference)
	my $fop = '';	# Format Options
	;    if ($FST == 1) {
# mkfs.vfat(8) - Linux manual page
# https://man7.org/linux/man-pages/man8/mkfs.vfat.8.html
		$$OBN .= sprintf "mkfs.vfat -F 32 -n %-9s/dev/\${sdX}%u\n", "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $PNO;	# for fat32
	} elsif ($FST == 2) {
# mkfs.ntfs(8): create NTFS file system - Linux man page
# https://linux.die.net/man/8/mkfs.ntfs
		$fop = '-f';	# quick (fast) format
		$$OBN .= sprintf "mkfs.%s -F -L %-9s%s /dev/\${sdX}%u\n", $FST[$FST], "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $fop, $PNO;	# for ntfs
	} elsif ($FST == 3) {
# mkfs.ext4(8): create ext2/ext3/ext4 filesystem - Linux man page
# https://linux.die.net/man/8/mkfs.ext4
# ext4(5) - Linux manual page
# https://man7.org/linux/man-pages/man5/ext4.5.html
# 64bit: Enables the file system to be larger than 2^32 blocks.
		$fop = $NPU > 16*$TiB ? '-O 64bit' : '-O ^64bit';	# set 64bit flag only if Normalized Partition Unit > 16TiB (block size 4KiB * 2**32 = 16TiB)
		$$OBN .= sprintf "mkfs.%s -F -L %-9s%s /dev/\${sdX}%u\n", $FST[$FST], "$PRE[$MBR*128+$PRE]$NAM[$VOL]", $fop, $PNO;	# for ext4
	}
}

print $OB1 if $OE1;

if ($WRP >= 1) {
	print <<EOW1;
unit MiB
print
unit s
print
quit
EOW1
}

if ($WRP >= 2) {
	print <<EOW2;
EOD

chmod 644 $FNB.txt
parted -s /dev/\${sdX} mktable $MBR[$MBR]
parted -a $ALT[$MBR] < $FNB.txt

$OB2
#gparted&
}

retry_$FNB () {
$OB3
#gparted&
}

if [ ! "\$2" = "-r" ]; then main_$FNB; else retry_$FNB; fi
EOW2
}
