# fdisk-lib.pl
# Functions for disk management under linux

do '../web-lib.pl';
&init_config();
&foreign_require("mount", "mount-lib.pl");
if (&foreign_check("raid")) {
	&foreign_require("raid", "raid-lib.pl");
	$raid_module++;
	}
if (&foreign_check("lvm")) {
	&foreign_require("lvm", "lvm-lib.pl");
	$lvm_module++;
	}
&foreign_require("proc", "proc-lib.pl");
%access = &get_module_acl();
$has_e2label = &has_command("e2label");
$has_xfs_db = &has_command("xfs_db");
$| = 1;

# list_disks_partitions()
# Returns a structure containing the details of all disks and partitions
sub list_disks_partitions
{
if (defined(@list_disks_partitions_cache)) {
	return @list_disks_partitions_cache;
	}

# Check /proc/scsi for SCSI disk models
local @pscsi;
open(SCSI, "/proc/scsi/scsi");
while(<SCSI>) {
	s/\s/ /g;
	if (/^Host:/) {
		push(@pscsi, $_);
		}
	elsif (/^\s+\S/) {
		$pscsi[$#pscsi] .= $_;
		}
	}
close(SCSI);
@pscsi = grep { /Type:\s+Direct-Access/i } @pscsi;

local (@disks, @devs, $d);
if (open(PARTS, "/proc/partitions")) {
	# The list of all disks can come from the kernel
	local $sc = 0;
	while(<PARTS>) {
		if (/\d+\s+\d+\s+\d+\s+sd(\S)\s/ ||
		    /\d+\s+\d+\s+\d+\s+(scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)\s+/) {
			# New or old style SCSI device
			local $d = $1;
			if ($pscsi[$sc] =~ /USB-FDU/) {
				# USB floppy with scsi emulation!
				splice(@pscsi, $sc, 1);
				next;
				}
			$sc++;
			push(@devs, $d =~ /^scsi/ ? "/dev/$d" : "/dev/sd$d");
			}
		elsif (/\d+\s+\d+\s+\d+\s+hd(\S)\s/) {
			# IDE disk (but skip CDs)
			local $n = $1;
			if (open(MEDIA, "/proc/ide/hd$n/media")) {
				local $media = <MEDIA>;
				close(MEDIA);
				if ($media =~ /^disk/) {
					push(@devs, "/dev/hd$n");
					}
				}
			}
		elsif (/\d+\s+\d+\s+\d+\s+(ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)\s+/) {
			# New-style IDE disk
			push(@devs, "/dev/$1");
			}
		elsif (/\d+\s+\d+\s+\d+\s+(rd\/c(\d+)d\d+)\s/) {
			# Mylex raid device
			push(@devs, "/dev/$1");
			}
		elsif (/\d+\s+\d+\s+\d+\s+(ida\/c(\d+)d\d+)\s/) {
			# Compaq raid device
			push(@devs, "/dev/$1");
			}
		elsif (/\d+\s+\d+\s+\d+\s+(cciss\/c(\d+)d\d+)\s/) {
			# Compaq Smart Array RAID
			push(@devs, "/dev/$1");
			}
		elsif (/\d+\s+\d+\s+\d+\s+(ataraid\/disc(\d+)\/disc)\s+/) {
			# Promise raid controller
			push(@devs, "/dev/$1");
			}
		}
	close(PARTS);
	@devs = sort { ($b =~ /\/hd[a-z]$/ ? 1 : 0) <=>
		       ($a =~ /\/hd[a-z]$/ ? 1 : 0) } @devs;
	}

# Call fdisk to get partition and geometry information
local @cdstat = stat("/dev/cdrom");
if (@cdstat) {
	@devs = grep { (stat($_))[1] != $cdstat[1] } @devs;
	}
local $devs = join(" ", @devs);
local ($disk, $m2);
open(FDISK, "fdisk -l $devs 2>/dev/null |");
while(<FDISK>) {
	if (/Disk\s+([^ :]+):\s+(\d+)\s+\S+\s+(\d+)\s+\S+\s+(\d+)/ ||
	    ($m2 = /Disk\s+([^ :]+):\s+(.*)\s+bytes/)) {
		# New disk section
		if ($m2) {
			$disk = { 'device' => $1,
				  'prefix' => $1 };
			<FDISK> =~ /(\d+)\s+\S+\s+(\d+)\s+\S+\s+(\d+)/ || next;
			$disk->{'heads'} = $1;
			$disk->{'sectors'} = $2;
			$disk->{'cylinders'} = $3;
			}
		else {
			$disk = { 'device' => $1,
				  'prefix' => $1,
				  'heads' => $2,
				  'sectors' => $3,
				  'cylinders' => $4 };
			}
		$disk->{'index'} = scalar(@disks);
		$disk->{'parts'} = [ ];
		local @st = stat($disk->{'device'});
		next if (@cdstat && $st[1] == $cdstat[1]);
		if ($disk->{'device'} =~ /\/sd(\S)$/) {
			# Old-style SCSI disk
			$disk->{'desc'} = &text('select_device', 'SCSI',uc($1));
			$disk->{'scsi'} = ord(uc($1))-65;
			$disk->{'type'} = 'scsi';
			}
		elsif ($disk->{'device'} =~ /\/hd(\S)$/) {
			# IDE disk
			$disk->{'desc'} = &text('select_device', 'IDE', uc($1));
			$disk->{'type'} = 'ide';
			}
		elsif ($disk->{'device'} =~ /\/(scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)/) {
			# New complete SCSI disk specification
			$disk->{'host'} = $2;
			$disk->{'bus'} = $3;
			$disk->{'target'} = $4;
			$disk->{'lun'} = $5;
			$disk->{'desc'} = &text('select_scsi',
						"$2", "$3", "$4", "$5");
			local $j;
			for($j=0; $j<@pscsi; $j++) {
				if ($pscsi[$j] =~ /Host:\s+scsi(\d+).*Id:\s+(\d+)/i && $disk->{'host'} == $1 && $disk->{'target'} == $2) {
					$disk->{'scsi'} = $j;
					last;
					}
				}
			$disk->{'type'} = 'scsi';
			$disk->{'prefix'} =~ s/disc$/part/g;
			}
		elsif ($disk->{'device'} =~ /\/(ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/disc)/) {
			# New-style IDE specification
			$disk->{'host'} = $2;
			$disk->{'bus'} = $3;
			$disk->{'target'} = $4;
			$disk->{'lun'} = $5;
			$disk->{'desc'} = &text('select_newide',
						"$2", "$3", "$4", "$5");
			$disk->{'type'} = 'ide';
			$disk->{'prefix'} =~ s/disc$/part/g;
			}
		elsif ($disk->{'device'} =~ /\/(rd\/c(\d+)d(\d+))/) {
			# Mylex raid device
			local ($mc, $md) = ($2, $3);
			$disk->{'desc'} = &text('select_mylex', $mc, $md);
			open(RD, "/proc/rd/c$mc/current_status");
			while(<RD>) {
				if (/^Configuring\s+(.*)/i) {
					$disk->{'model'} = $1;
					}
				elsif (/\s+(\S+):\s+([^, ]+)/ &&
				       $1 eq $disk->{'device'}) {
					$disk->{'raid'} = $2;
					}
				}
			close(RD);
			$disk->{'type'} = 'raid';
			$disk->{'prefix'} = $disk->{'device'}.'p';
			}
		elsif ($disk->{'device'} =~ /\/(ida\/c(\d+)d(\d+))/) {
			# Compaq RAID device
			local ($ic, $id) = ($2, $3);
			$disk->{'desc'} = &text('select_cpq', $ic, $id);
			open(IDA, -d "/proc/driver/array" ? "/proc/driver/array/ida$ic" : "/proc/driver/cpqarray/ida$ic");
			while(<IDA>) {
				if (/^(\S+):\s+(.*)/ && $1 eq "ida$ic") {
					$disk->{'model'} = $2;
					}
				}
			close(IDA);
			$disk->{'type'} = 'raid';
			$disk->{'prefix'} = $disk->{'device'}.'p';
			}
		elsif ($disk->{'device'} =~ /\/(cciss\/c(\d+)d(\d+))/) {
			# Compaq Smart Array RAID
			local ($ic, $id) = ($2, $3);
			$disk->{'desc'} = &text('select_smart', $ic, $id);
			open(CCI, "/proc/driver/cciss/cciss$ic");
			while(<CCI>) {
				if (/^\s*(\S+):\s*(.*)/ && $1 eq "cciss$ic") {
					$disk->{'model'} = $2;
					}
				}
			close(CCI);
			$disk->{'type'} = 'raid';
			$disk->{'prefix'} = $disk->{'device'}.'p';
			}
		elsif ($disk->{'device'} =~ /\/(ataraid\/disc(\d+)\/disc)/) {
			# Promise RAID controller
			local $dd = $2;
			$disk->{'desc'} = &text('select_promise', $dd);
			$disk->{'type'} = 'raid';
			$disk->{'prefix'} =~ s/disc$/part/g;
			}
		push(@disks, $disk);
		}
	elsif (/(\/dev\/\S+?(\d+))[ \t*]+\d+\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S{1,2})\s+(.*)/ || /(\/dev\/\S+?(\d+))[ \t*]+(\d+)\s+(\d+)\s+(\S+)\s+(\S{1,2})\s+(.*)/) {
		# Partition within the current disk
		local $part = { 'number' => $2,
				'device' => $1,
				'type' => $6,
				'start' => $3,
				'end' => $4,
				'blocks' => $5,
				'extended' => $6 eq '5' || $6 eq 'f' ? 1 : 0,
				'index' => scalar(@{$disk->{'parts'}}) };
		$part->{'desc'} =
			$part->{'device'} =~ /(.)d(\S)(\d+)$/ ?
			 &text('select_part', $1 eq 's' ? 'SCSI' : 'IDE', uc($2), "$3") :
			$part->{'device'} =~ /scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ?
			 &text('select_spart', "$1", "$2", "$3", "$4", "$5") :
			$part->{'device'} =~ /ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ?
			 &text('select_snewide', "$1", "$2", "$3", "$4", "$5") :
			$part->{'device'} =~ /rd\/c(\d+)d(\d+)p(\d+)$/ ? 
			 &text('select_mpart', "$1", "$2", "$3") :
			$part->{'device'} =~ /ida\/c(\d+)d(\d+)p(\d+)$/ ? 
			 &text('select_cpart', "$1", "$2", "$3") :
			$part->{'device'} =~ /cciss\/c(\d+)d(\d+)p(\d+)$/ ? 
			 &text('select_smartpart', "$1", "$2", "$3") :
			$part->{'device'} =~ /ataraid\/disc(\d+)\/part(\d+)$/ ?
			 &text('select_ppart', "$1", "$2") :
			"???",
		push(@{$disk->{'parts'}}, $part);
		}
	}
close(FDISK);

# Check /proc/ide for IDE disk models
foreach $d (@disks) {
	if ($d->{'type'} eq 'ide') {
		local $short;
		if (defined($d->{'host'})) {
			$short = "hd".(('a' .. 'z')[$d->{'host'}*4 + $d->{'target'}*2 + $d->{'bus'}]);
			}
		else {
			$short = $d->{'device'};
			$short =~ s/^.*\///g;
			}
		if (open(MODEL, "/proc/ide/$short/model")) {
			($d->{'model'} = <MODEL>) =~ s/\r|\n//g;
			close(MODEL);
			}
		}
	}

# Fill in SCSI information
foreach $d (@disks) {
	if ($d->{'type'} eq 'scsi') {
		local $s = $d->{'scsi'};
		if ($pscsi[$s] =~ /Vendor:\s+(\S+).*Model:\s+(.*)\s+Rev:/i) {
			$d->{'model'} = "$1 $2";
			}
		if ($pscsi[$s] =~ /Host:\s+scsi(\d+).*Id:\s+(\d+)/i) {
			$d->{'controller'} = int($1);
			$d->{'scsiid'} = int($2);
			}
		}
	}

@list_disks_partitions_cache = @disks;
return @disks;
}

# change_type(disk, partition, type)
# Changes the type of an existing partition
sub change_type
{
&open_fdisk("$_[0]");
&wprint("t\n");
&wait_for($fh, 'Partition.*:');
&wprint("$_[1]\n");
&wait_for($fh, 'Hex.*:');
&wprint("$_[2]\n");
&wait_for($fh, 'Command.*:');
&wprint("w\n"); sleep(1);
&close_fdisk();
}

# delete_partition(disk, partition)
# Delete an existing partition
sub delete_partition
{
&open_fdisk("$_[0]");
&wprint("d\n");
&wait_for($fh, 'Partition.*:');
&wprint("$_[1]\n");
&wait_for($fh, 'Command.*:');
&wprint("w\n");
&wait_for($fh, 'Syncing'); sleep(3);
&close_fdisk();
}

# create_partition(disk, partition, start, end, type)
# Create a new partition with the given extent and type
sub create_partition
{
&open_fdisk("$_[0]");
&wprint("n\n");
local $wf = &wait_for($fh, 'primary.*\r\n', 'First.*:');
if ($_[1] > 4) {
	&wprint("l\n");
	}
else {
	&wprint("p\n");
	&wait_for($fh, 'Partition.*:');
	&wprint("$_[1]\n");
	}
&wait_for($fh, 'First.*:') if ($wf != 1);
&wprint("$_[2]\n");
&wait_for($fh, 'Last.*:');
&wprint("$_[3]\n");
&wait_for($fh, 'Command.*:');

&wprint("t\n");
&wait_for($fh, 'Partition.*:');
&wprint("$_[1]\n");
&wait_for($fh, 'Hex.*:');
&wprint("$_[4]\n");
&wait_for($fh, 'Command.*:');
&wprint("w\n");
&wait_for($fh, 'Syncing'); sleep(3);
&close_fdisk();
}

# create_extended(disk, partition, start, end)
sub create_extended
{
&open_fdisk("$_[0]");
&wprint("n\n");
&wait_for($fh, 'primary.*\r\n');
&wprint("e\n");
&wait_for($fh, 'Partition.*:');
&wprint("$_[1]\n");
&wait_for($fh, 'First.*:');
&wprint("$_[2]\n");
&wait_for($fh, 'Last.*:');
&wprint("$_[3]\n");
&wait_for($fh, 'Command.*:');

&wprint("w\n");
&wait_for($fh, 'Syncing'); sleep(3);
&close_fdisk();
}

# list_tags()
# Returns a list of known partition tag numbers
sub list_tags
{
return sort { hex($a) <=> hex($b) } (keys %tags);
}

# tag_name(tag)
# Returns a human-readable version of a tag
sub tag_name
{
return $tags{$_[0]} ? $tags{$_[0]}
		    : $hidden_tags{$_[0]};
}

# conv_type(tag)
# Given a partition tag, returns the filesystem type (assuming it is supported)
sub conv_type
{
if ($_[0] eq "4" || $_[0] eq "6" ||
    $_[0] eq "1" || $_[0] eq "e") { $rv = "msdos"; }
elsif ($_[0] eq "b" || $_[0] eq "c") { return "vfat"; }
elsif ($_[0] eq "83") { $rv = "ext2"; }
elsif ($_[0] eq "81") { $rv = "minix"; }
else { return undef; }
if (&has_command("mkfs.$rv")) { return $rv; }
return undef;
}

# fstype_name(type)
# Returns a readable name for a filesystem type
sub fstype_name
{
return $text{"fs_".$_[0]};
}

sub mkfs_options
{
if ($_[0] eq "ext2") {
	&opt_input("ext2_b", $text{'bytes'}, 1);
	&opt_input("ext2_f", $text{'bytes'}, 0);
	&opt_input("ext2_i", "", 1);
	&opt_input("ext2_m", "%", 0);
	&opt_input("ext2_g", "", 1);
	print "<td align=right><b>$text{'ext2_c'}</b></td>\n";
	print "<td><input type=radio name=ext2_c value=1> $text{'yes'}\n";
	print "<input type=radio name=ext2_c value=0 checked> $text{'no'}",
	      "</td> </tr>\n";
	}
elsif ($_[0] eq "msdos" || $_[0] eq "vfat") {
	&opt_input("msdos_ff", "", 1);
	&opt_input("msdos_F", "bits", 0);
	&opt_input("msdos_i", "", 1);
	&opt_input("msdos_n", "", 0);
	&opt_input("msdos_r", "", 1);
	&opt_input("msdos_s", "sectors", 0);
	print "<tr> <td align=right><b>$text{'msdos_c'}</b></td>\n";
	print "<td><input type=radio name=msdos_c value=1> $text{'yes'}\n";
	print "<input type=radio name=msdos_c value=0 checked> $text{'no'}",
	      "</td> </tr>\n";
	}
elsif ($_[0] eq "minix") {
	&opt_input("minix_n", "", 1);
	&opt_input("minix_i", "", 0);
	&opt_input("minix_b", "", 1);
	print "<td align=right><b>$text{'minix_c'}</b></td>\n";
	print "<td><input type=radio name=msdos_c value=1> $text{'yes'}\n";
	print "<input type=radio name=msdos_c value=0 checked> $text{'no'}",
	      "</td> </tr>\n";
	}
elsif ($_[0] eq "reiserfs") {
	print "<tr> <td><b>$text{'reiserfs_force'}</b></td>\n";
	print "<td><input type=radio name=reiserfs_f value=1> $text{'yes'}\n";
	print "<input type=radio name=reiserfs_f value=0 checked> ",
	      "$text{'no'}</td>\n";

	print "<td><b>$text{'reiserfs_hash'}</b></td>\n";
	print "<td><select name=reiserfs_h>\n";
	print "<option value='' checked>$text{'default'}\n";
	print "<option>rupasov\n";
	print "<option>tea\n";
	print "</select></td> </tr>\n";
	}
elsif ($_[0] eq "ext3") {
	&opt_input("ext2_b", $text{'bytes'}, 1);
	&opt_input("ext2_f", $text{'bytes'}, 0);
	&opt_input("ext2_i", "", 1);
	&opt_input("ext2_m", "%", 0);
	&opt_input("ext3_j", "MB", 1);
	print "<td align=right><b>$text{'ext2_c'}</b></td>\n";
	print "<td><input type=radio name=ext2_c value=1> $text{'yes'}\n";
	print "<input type=radio name=ext2_c value=0 checked> $text{'no'}",
	      "</td> </tr>\n";
	}
elsif ($_[0] eq "xfs") {
	print "<tr> <td><b>$text{'xfs_force'}</b></td>\n";
	print "<td><input type=radio name=xfs_f value=1> $text{'yes'}\n";
	print "<input type=radio name=xfs_f value=0 checked> ",
	      "$text{'no'}</td>\n";

	&opt_input("xfs_b", $text{'bytes'}, 0);
	}
elsif ($_[0] eq "jfs") {
	&opt_input("jfs_s", $text{'megabytes'}, 1);
	print "<td align=right><b>$text{'jfs_c'}</b></td>\n";
	print "<td><input type=radio name=jfs_c value=1> $text{'yes'}\n";
	print "<input type=radio name=jfs_c value=0 checked> $text{'no'}",
	      "</td> </tr>\n";
	}
}

# mkfs_parse(type, device)
# Returns a command to build a new filesystem of the given type on the
# given device. Options are taken from %in.
sub mkfs_parse
{
local($cmd);
if ($_[0] eq "ext2") {
	$cmd = "mkfs -t ext2";
	$cmd .= &opt_check("ext2_b", '\d+', "-b");
	$cmd .= &opt_check("ext2_f", '\d+', "-f");
	$cmd .= &opt_check("ext2_i", '\d{4,}', "-i");
	$cmd .= &opt_check("ext2_m", '\d+', "-m");
	$cmd .= &opt_check("ext2_g", '\d+', "-g");
	$cmd .= $in{'ext2_c'} ? " -c" : "";
	$cmd .= " $_[1]";
	}
elsif ($_[0] eq "msdos" || $_[0] eq "vfat") {
	$cmd = "mkfs -t $_[0]";
	$cmd .= &opt_check("msdos_ff", '[1-2]', "-f");
	$cmd .= &opt_check("msdos_F", '\d+', "-F");
	$cmd .= &opt_check("msdos_i", '[0-9a-f]{8}', "-i");
	$cmd .= &opt_check("msdos_n", '\S{1,11}', "-n");
	$cmd .= &opt_check("msdos_r", '\d+', "-r");
	$cmd .= &opt_check("msdos_s", '\d+', "-s");
	$cmd .= $in{'msdos_c'} ? " -c" : "";
	$cmd .= " $_[1]";
	}
elsif ($_[0] eq "minix") {
	local(@plist, $disk, $part, $i, @pinfo);
	$cmd = "mkfs -t minix";
	$cmd .= &opt_check("minix_n", '14|30', "-n ");
	$cmd .= &opt_check("minix_i", '\d+', "-i ");
	$cmd .= $in{'minix_c'} ? " -c" : "";
	$cmd .= &opt_check("minix_b", '\d+', " ");
	$cmd .= " $_[1]";
	}
elsif ($_[0] eq "reiserfs") {
	$cmd = "yes | mkreiserfs";
	$cmd .= " -f" if ($in{'reiserfs_f'});
	$cmd .= " -h $in{'reiserfs_h'}" if ($in{'reiserfs_h'});
	$cmd .= " $_[1]";
	}
elsif ($_[0] eq "ext3") {
	if (&has_command("mkfs.ext3")) {
		$cmd = "mkfs -t ext3";
		$cmd .= &opt_check("ext3_j", '\d+', "-j");
		}
	elsif (&has_command("mke3fs")) {
		$cmd = "mke3fs";
		$cmd .= &opt_check("ext3_j", '\d+', "-j");
		}
	else {
		$cmd = "mkfs.ext2 -j";
		if (!$in{'ext3_j_def'}) {
			$in{'ext3_j'} =~ /^\d+$/ ||
				&error(&text('opt_error', $in{'ext3_j'},
					     $text{'ext3_j'}));
			$cmd .= " -J size=$in{'ext3_j'}";
			}
		}
	$cmd .= &opt_check("ext2_b", '\d+', "-b");
	$cmd .= &opt_check("ext2_f", '\d+', "-f");
	$cmd .= &opt_check("ext2_i", '\d{4,}', "-i");
	$cmd .= &opt_check("ext2_m", '\d+', "-m");
	$cmd .= $in{'ext2_c'} ? " -c" : "";
	$cmd .= " $_[1]";
	}
elsif ($_[0] eq "xfs") {
	$cmd = "mkfs -t $_[0]";
	$cmd .= " -f" if ($in{'xfs_f'});
	$cmd .= " -b size=$in{'xfs_b'}" if (!$in{'xfs_b_def'});
	$cmd .= " $_[1]";
	}
elsif ($_[0] eq "jfs") {
	$cmd = "mkfs -t $_[0] -q";
	$cmd .= &opt_check("jfs_s", '\d+', "-s");
	$cmd .= " -c" if ($in{'jfs_c'});
	$cmd .= " $_[1]";
	}
return $cmd;
}

# can_tune(type)
# Returns 1 if this filesystem type can be tuned
sub can_tune
{
return ($_[0] eq "ext2");
}

# tunefs_options(type)
# Output HTML for tuning options for some filesystem type
sub tunefs_options
{
if ($_[0] eq "ext2") {
	&opt_input("tunefs_c", "", 1);

	print "<td align=right><b>$text{'tunefs_e'}</b></td> <td>\n";
	print "<input type=radio name=tunefs_e_def value=1 checked> ",
	      "$text{'opt_default'}\n";
	print "&nbsp; <input type=radio name=tunefs_e_def value=0>\n";
	print "<select name=tunefs_e>\n";
	print "<option value=continue> $text{'tunefs_continue'}\n";
	print "<option value=remount-ro> $text{'tunefs_remount'}\n";
	print "<option value=panic> $text{'tunefs_panic'}\n";
	print "</select></td> </tr>\n";

	print "<tr> <td align=right><b>$text{'tunefs_u'}</b></td> <td>\n";
	print "<input type=radio name=tunefs_u_def value=1 checked> ",
	      "$text{'opt_default'}\n";
	print "&nbsp; <input type=radio name=tunefs_u_def value=0>\n";
	print "<input name=tunefs_u size=8> ",
	      &user_chooser_button("tunefs_u", 0),"</td>\n";

	print "<td align=right><b>$text{'tunefs_g'}</b></td> <td>\n";
	print "<input type=radio name=tunefs_g_def value=1 checked> ",
	      "$text{'opt_default'}\n";
	print "&nbsp; <input type=radio name=tunefs_g_def value=0>\n";
	print "<input name=tunefs_g size=8> ",
	      &group_chooser_button("tunefs_g", 0),"</td> </tr>\n";

	&opt_input("tunefs_m", "%", 1);
	$tsel = "<select name=tunefs_i_unit>\n".
		"<option value=d> $text{'tunefs_days'}\n".
		"<option value=w> $text{'tunefs_weeks'}\n".
		"<option value=m> $text{'tunefs_months'}\n".
		"</select>\n";
	&opt_input("tunefs_i", $tsel, 0);
	}
}

# tunefs_parse(type, device)
# Returns the tuning command based on user inputs
sub tunefs_parse
{
if ($_[0] eq "ext2") {
	$cmd = "tune2fs";
	$cmd .= &opt_check("tunefs_c", '\d+', "-c");
	$cmd .= $in{'tunefs_e_def'} ? "" : " -e$in{'tunefs_e'}";
	$cmd .= $in{'tunefs_u_def'} ? "" : " -u".getpwnam($in{'tunefs_u'});
	$cmd .= $in{'tunefs_g_def'} ? "" : " -g".getgrnam($in{'tunefs_g'});
	$cmd .= &opt_check("tunefs_m",'\d+',"-m");
	$cmd .= &opt_check("tunefs_i", '\d+', "-i").
		($in{'tunefs_i_def'} ? "" : $in{'tunefs_i_unit'});
	$cmd .= " $_[1]";
	}
return $cmd;
}

# need_reboot(disk)
# Returns 1 if a reboot is needed after changing the partitions on some disk
sub need_reboot
{
local $un = `uname -r`;
return $un =~ /^2\.0\./ || $un =~ /^1\./ || $un =~ /^0\./;
}

# device_status(device)
# Returns an array of  directory, type, mounted
sub device_status
{
@mounted = &foreign_call("mount", "list_mounted") if (!@mounted);
@mounts = &foreign_call("mount", "list_mounts") if (!@mounts);
local $label = &get_label($_[0]);

local ($mounted) = grep { &same_file($_->[1], $_[0]) ||
			  $_->[1] eq "LABEL=$label" } @mounted;
local ($mount) = grep { &same_file($_->[1], $_[0]) ||
			$_->[1] eq "LABEL=$label" } @mounts;
if ($mounted) { return ($mounted->[0], $mounted->[2], 1,
			&indexof($mount, @mounts),
			&indexof($mounted, @mounted)); }
elsif ($mount) { return ($mount->[0], $mount->[2], 0,
			 &indexof($mount, @mounts)); }
if ($raid_module) {
	$raidconf = &foreign_call("raid", "get_raidtab") if (!$raidconf);
	foreach $c (@$raidconf) {
		foreach $d (&raid::find_value('device', $c->{'members'})) {
			return ( $c->{'value'}, "raid", 1 ) if ($d eq $_[0]);
			}
		}
	}
if ($lvm_module) {
	if (!defined(@physical_volumes)) {
		@physical_volumes = ();
		foreach $vg (&foreign_call("lvm", "list_volume_groups")) {
			push(@physical_volumes,
				&foreign_call("lvm", "list_physical_volumes",
						     $vg->{'name'}));
			}
		}
	foreach $pv (@physical_volumes) {
		return ( $pv->{'vg'}, "lvm", 1)
			if ($pv->{'device'} eq $_[0]);
		}
	}
return ();
}

# can_fsck(type)
# Returns 1 if some filesystem type can fsck'd
sub can_fsck
{
return ($_[0] eq "ext2" && &has_command("fsck.ext2") ||
	$_[0] eq "minix" && &has_command("fsck.minix"));
}

# fsck_command(type, device)
# Returns the fsck command to unconditionally check a filesystem
sub fsck_command
{
if ($_[0] eq "ext2") {
	return "fsck -t ext2 -p $_[1]";
	}
elsif ($_[0] eq "minix") {
	return "fsck -t minix -a $_[1]";
	}
}

# fsck_error(code)
# Returns a description of an exit code from fsck
sub fsck_error
{
return $text{"fsck_err$_[0]"} ? $text{"fsck_err$_[0]"}
			      : &text("fsck_unknown", $_[0]);
}

# partition_select(name, value, mode, [&found], [disk_regexp])
# Returns HTML for selecting a disk or partition
# mode 0 = floppies and disk partitions
#      1 = disks
#      2 = floppies and disks and disk partitions
#      3 = disk partitions
sub partition_select
{
local $rv = "<select name=$_[0]>\n";
local ($found, $d, $p);
if ($_[2] == 0 || $_[2] == 2) {
	$rv .= sprintf "<option %s value=/dev/fd0>%s\n",
		$_[1] eq "/dev/fd0" ? "selected" : "",
		&text('select_fd', 0) if (!$_[4] || "/dev/fd0" =~ /$_[4]/);
	$rv .= sprintf "<option %s value=/dev/fd1>%s\n",
		$_[1] eq "/dev/fd1" ? "selected" : "",
		&text('select_fd', 1) if (!$_[4] || "/dev/fd1" =~ /$_[4]/);
	$found++ if ($_[1] =~ /^\/dev\/fd[01]$/);
	}
local @dlist = &list_disks_partitions();
foreach $d (@dlist) {
	local $dev = $d->{'device'};
	next if ($_[4] && $dev !~ /$_[4]/);
	if ($_[2] == 1 || $_[2] == 2) {
		local $name = $d->{'desc'};
		$name .= " ($d->{'model'})" if ($d->{'model'});
		$rv .= sprintf "<option value=%s %s>%s\n",
			$dev, $_[1] eq $dev ? "selected" : "", $name;
		$found++ if ($dev eq $_[1]);
		}
	if ($_[2] == 0 || $_[2] == 2 || $_[2] == 3) {
		foreach $p (@{$d->{'parts'}}) {
			next if ($p->{'extended'});
			local $name = $p->{'desc'};
			$name .= " (".&tag_name($p->{'type'}).")"
				if (&tag_name($p->{'type'}));
			$rv .= sprintf "<option %s value=%s>%s\n",
				  $_[1] eq $p->{'device'} ? "selected" : "",
				  $p->{'device'}, $name;
			$found++ if ($_[1] eq $p->{'device'});
			}
		}
	}
if (!$found && $_[1] && !$_[3]) {
	$rv .= "<option selected>$_[1]\n";
	}
if ($_[3]) {
	${$_[3]} = $found;
	}
$rv .= "</select>\n";
return $rv;
}

# label_select(name, value, &found)
sub label_select
{
local $rv = "<select name=$_[0]>\n";
local @dlist = &list_disks_partitions();
local $any;
foreach $d (@dlist) {
	local $dev = $d->{'device'};
	foreach $p (@{$d->{'parts'}}) {
		next if ($p->{'type'} ne '83');
		local $label = &get_label($p->{'device'});
		next if (!$label);
		$rv .= sprintf "<option %s value=%s>%s (%s)\n",
			  $_[1] eq $label ? "selected" : "",
			  $label, $label, $p->{'desc'};
		${$_[2]}++ if ($_[1] eq $label);
		$any++;
		}
	}
if (!$found && $_[1] && !$_[2]) {
	$rv .= "<option selected>$_[1]\n";
	}
$rv .= "</select>\n";
return $any ? $rv : undef;
}

#############################################################################
# Internal functions
#############################################################################
sub open_fdisk
{
local $fpath = &check_fdisk();
($fh, $fpid) = &foreign_call("proc", "pty_process_exec", join(" ",$fpath, @_));
}

sub open_sfdisk
{
local $sfpath = &has_command("sfdisk");
($fh, $fpid) = &foreign_call("proc", "pty_process_exec", join(" ",$sfpath, @_));
}

sub check_fdisk
{
local $fpath = &has_command("fdisk");
&error(&text('open_error', "<tt>fdisk</tt>")) if (!$fpath);
return $fpath;
}

sub close_fdisk
{
close($fh); kill('TERM', $fpid);
}

sub wprint
{
syswrite($fh, $_[0], length($_[0]));
}

sub opt_input
{
print $_[2] ? "<tr>" : "";
print "<td align=right><b>$text{$_[0]}</b></td>\n";
print "<td nowrap><input type=radio name=$_[0]_def value=1 checked> ",
      $text{'opt_default'},"\n";
print "&nbsp; <input type=radio name=$_[0]_def value=0>\n";
print "<input name=$_[0] size=6> $_[1]</td>";
print $_[2] ? "\n" : "</tr>\n";
}

sub opt_check
{
if ($in{"$_[0]_def"}) { return ""; }
elsif ($in{$_[0]} !~ /^$_[1]$/) {
	&error(&text('opt_error', $in{$_[0]}, $text{$_[0]}));
	}
else { return " $_[2] $in{$_[0]}"; }
}

%tags = ('0', 'Empty',
	 '1', 'FAT12',
	 '2', 'XENIX root',
	 '3', 'XENIX usr',
	 '4', 'FAT16 <32M',
	 '6', 'FAT16',
	 '7', 'HPFS/NTFS',
	 '8', 'AIX',
	 '9', 'AIX bootable',
	 'a', 'OS/2 boot manager',
	 'b', 'Win95 FAT32',
	 'c', 'Win95 FAT32 LBA',
	 'e', 'Win95 FAT16 LBA',
	'10', 'OPUS',
	'11', 'Hidden FAT12',
	'12', 'Compaq diagnostic',
	'14', 'Hidden FAT16 < 32M',
	'16', 'Hidden FAT16',
	'17', 'Hidden HPFS/NTFS',
	'18', 'AST Windows swapfile',
	'1b', 'Hidden Win95 FAT (1b)',
	'1c', 'Hidden Win95 FAT (1c)',
	'1e', 'Hidden Win95 FAT (1e)',
	'24', 'NEC DOS',
	'3c', 'PartitionMagic recovery',
	'40', 'Venix 80286',
	'41', 'PPC PReP boot',
	'42', 'SFS',
	'4d', 'QNX 4.x',
	'4e', 'QNX 4.x 2nd partition',
	'4f', 'QNX 4.x 3rd partition',
	'50', 'OnTrack DM',
	'51', 'OnTrack DM6 Aux1',
	'52', 'CP/M',
	'53', 'OnTrack DM6 Aux3',
	'54', 'OnTrack DM6',
	'55', 'EZ-Drive',
	'56', 'Golden Bow',
	'5c', 'Priam Edisk',
	'61', 'SpeedStor',
	'63', 'GNU HURD or SysV',
	'64', 'Novell Netware 286',
	'65', 'Novell Netware 386',
	'70', 'DiskSecure Multi-Boot',
	'75', 'PC/IX',
	'80', 'Old Minix',
	'81', 'Minix / Old Linux / Solaris',
	'82', 'Linux swap',
	'83', 'Linux',
	'84', 'OS/2 hidden C: drive',
	'85', 'Linux extended',
	'86', 'NTFS volume set (86)',
	'87', 'NTFS volume set (87)',
	'8e', 'Linux LVM',
	'93', 'Amoeba',
	'94', 'Amoeba BBT',
	'a0', 'IBM Thinkpad hibernation',
	'a5', 'BSD/386',
	'a6', 'OpenBSD',
	'a7', 'NeXTSTEP',
	'b7', 'BSDI filesystem',
	'b8', 'BSDI swap',
	'c1', 'DRDOS/sec FAT12',
	'c4', 'DRDOS/sec FAT16 <32M',
	'c6', 'DRDOS/sec FAT16',
	'c7', 'Syrinx',
	'db', 'CP/M / CTOS',
	'e1', 'DOS access',
	'e3', 'DOS read-only',
	'e4', 'SpeedStor',
	'eb', 'BeOS',
	'f1', 'SpeedStor',
	'f4', 'SpeedStor large partition',
	'f2', 'DOS secondary',
	'fd', 'Linux raid',
	'fe', 'LANstep',
	'ff', 'BBT'
	);

%hidden_tags = (
	 '5', 'Extended',
	 'f', 'Win95 extended LBA',
	);
	
@space_type = ( '1', '4', '5', '6', 'b', 'c', 'e', '83' );

# can_edit_disk(device)
sub can_edit_disk
{
foreach (split(/\s+/, $access{'disks'})) {
        return 1 if ($_ eq "*" || $_ eq $_[0]);
        }
return 0;
}

# disk_space(device)
# Returns the amount of total and free space for some filesystem, or an
# empty array if not appropriate.
sub disk_space
{
`df -k $_[0]` =~ /Mounted on\n\S+\s+(\S+)\s+\S+\s+(\S+)/ || return ();
return ($1, $2);
}

# supported_filesystems()
# Returns a list of filesystem types that can have mkfs_options called on them
sub supported_filesystems
{
local @fstypes = ( "ext2" );
push(@fstypes, "ext3") if (&has_command("mkfs.ext3") ||
			   &has_command("mke3fs") ||
			   `mkfs.ext2 -h 2>&1` =~ /\[-j\]/);
push(@fstypes, "reiserfs") if (&has_command("mkreiserfs"));
push(@fstypes, "xfs") if (&has_command("mkfs.xfs"));
push(@fstypes, "jfs") if (&has_command("mkfs.jfs"));
push(@fstypes, "msdos");
push(@fstypes, "vfat");
push(@fstypes, "minix");
return @fstypes;
}

# get_label(device, [type])
# Returns the XFS or EXT label for some device's filesystem
sub get_label
{
local $label;
if ($has_e2label) {
	$label = `e2label $_[0] 2>&1`;
	chop($label);
	}
if (($? || $label !~ /\S/) && $has_xfs_db) {
	$label = undef;
	local $out = `xfs_db -x -p xfs_admin -c label -r $_[0] 2>&1`;
	$label = $1 if ($out =~ /label\s*=\s*"(.*)"/ &&
			$1 ne '(null)');
	}
return $? || $label !~ /\S/ ? undef : $label;
}

# set_label(device, label, [type])
# Tries to set the label for some device's filesystem
sub set_label
{
if ($has_e2label && ($_[2] =~ /^ext[23]$/ || !$_[2])) {
	&system_logged("e2label '$_[0]' '$_[1]' >/dev/null 2>&1");
	return 1 if (!$?);
	}
if ($has_xfs_db && ($_[2] eq "xfs" || !$_[2])) {
	&system_logged("xfs_db -x -p xfs_admin -c \"label $_[1]\" $_[0] >/dev/null 2>&1");
	return 1 if (!$?);
	}
return 0;
}
