# fdisk-lib.pl # Functions for disk management under linux BEGIN { push(@INC, ".."); }; use WebminCore; &init_config(); &foreign_require("mount", "mount-lib.pl"); if (&foreign_check("raid")) { &foreign_require("raid"); $raid_module++; } if (&foreign_check("lvm")) { &foreign_require("lvm"); $lvm_module++; } if (&foreign_check("iscsi-server")) { &foreign_require("iscsi-server"); $iscsi_server_module++; } if (&foreign_check("iscsi-target")) { &foreign_require("iscsi-target"); $iscsi_target_module++; } &foreign_require("proc", "proc-lib.pl"); %access = &get_module_acl(); $has_e2label = &has_command("e2label"); $has_xfs_db = &has_command("xfs_db"); $has_volid = &has_command("vol_id"); $has_reiserfstune = &has_command("reiserfstune"); $uuid_directory = "/dev/disk/by-uuid"; if ($config{'mode'} eq 'parted') { $has_parted = 1; } elsif ($config{'mode'} eq 'fdisk') { $has_parted = 0; } else { $has_parted = !$config{'noparted'} && &has_command("parted") && &get_parted_version() >= 1.8; } $| = 1; # list_disks_partitions([include-cds]) # Returns a structure containing the details of all disks and partitions sub list_disks_partitions { if (scalar(@list_disks_partitions_cache)) { return @list_disks_partitions_cache; } local (@pscsi, @dscsi, $dscsi_mode); if (-r "/proc/scsi/sg/devices" && -r "/proc/scsi/sg/device_strs") { # Get device info from various /proc/scsi files open(DEVICES, "/proc/scsi/sg/devices"); while() { s/\r|\n//g; local @l = split(/\t+/, $_); push(@dscsi, { 'host' => $l[0], 'bus' => $l[1], 'target' => $l[2], 'lun' => $l[3], 'type' => $l[4] }); } close(DEVICES); local $i = 0; open(DEVNAMES, "/proc/scsi/sg/device_strs"); while() { s/\r|\n//g; local @l = split(/\t+/, $_); $dscsi[$i]->{'make'} = $l[0]; $dscsi[$i]->{'model'} = $l[1]; $i++; } close(DEVNAMES); $dscsi_mode = 1; @dscsi = grep { $_->{'type'} == 0 } @dscsi; } else { # Check /proc/scsi/scsi for SCSI disk models open(SCSI, "/proc/scsi/scsi"); local @lines = ; close(SCSI); if ($lines[0] =~ /^Attached\s+domains/i) { # New domains format local $dscsi; foreach (@lines) { s/\s/ /g; if (/Device:\s+(.*)(sd[a-z]+)\s+usage/) { $dscsi = { 'dev' => $2 }; push(@dscsi, $dscsi); } elsif (/Device:/) { $dscsi = undef; } elsif (/Vendor:\s+(\S+)\s+Model:\s+(\S+)/ && $dscsi) { $dscsi->{'make'} = $1; $dscsi->{'model'} = $2; } elsif (/Host:\s+scsi(\d+)\s+Channel:\s+(\d+)\s+Id:\s+(\d+)\s+Lun:\s+(\d+)/ && $dscsi) { $dscsi->{'host'} = $1; $dscsi->{'bus'} = $2; $dscsi->{'target'} = $3; $dscsi->{'lun'} = $4; } } $dscsi_mode = 1; } else { # Standard format foreach (@lines) { s/\s/ /g; if (/^Host:/) { push(@pscsi, $_); } elsif (/^\s+\S/ && @pscsi) { $pscsi[$#pscsi] .= $_; } } @pscsi = grep { /Type:\s+Direct-Access/i } @pscsi; $dscsi_mode = 0; } } local (@disks, @devs, $d); if (open(PARTS, "/proc/partitions")) { # The list of all disks can come from the kernel local $sc = 0; while() { if (/\d+\s+\d+\s+\d+\s+sd([a-z]+)\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; local ($host, $bus, $target, $lun) = ($2, $3, $4, $5); if (!$dscsi_mode && $pscsi[$sc] =~ /USB-FDU/) { # USB floppy with scsi emulation! splice(@pscsi, $sc, 1); next; } if ($host ne '') { local $scsidev = "/dev/$d"; if (!-r $scsidev) { push(@devs, "/dev/". &number_to_device("sd", $sc)); } else { push(@devs, $scsidev); } } else { push(@devs, "/dev/sd$d"); } $sc++; } elsif (/\d+\s+\d+\s+\d+\s+hd([a-z]+)\s/) { # IDE disk (but skip CDs) local $n = $1; if (open(MEDIA, "/proc/ide/hd$n/media")) { local $media = ; close(MEDIA); if ($media =~ /^disk/ && !$_[0]) { 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 local $idedev = "/dev/$1"; local ($host, $bus, $target, $lun) = ($2, $3, $4, $5); if (!-r $idedev) { push(@devs, "/dev/". &hbt_to_device($host, $bus, $target)); } else { 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"); } elsif (/\d+\s+\d+\s+\d+\s+(vd[a-z]+)\s/) { # Virtio disk from KVM push(@devs, "/dev/$1"); } elsif (/\d+\s+\d+\s+\d+\s+(xvd[a-z]+)\s/) { # PV disk from Xen push(@devs, "/dev/$1"); } } close(PARTS); # Sort IDE first @devs = sort { ($b =~ /\/hd[a-z]+$/ ? 1 : 0) <=> ($a =~ /\/hd[a-z]+$/ ? 1 : 0) } @devs; } return ( ) if (!@devs); # No disks, ie on Xen # Skip cd-rom drive, identified from symlink. Don't do this if we can identify # cds by their media type though if (!-d "/proc/ide") { local @cdstat = stat("/dev/cdrom"); if (@cdstat && !$_[0]) { @devs = grep { (stat($_))[1] != $cdstat[1] } @devs; } } # Get Linux disk ID mapping local %id_map; local $id_dir = "/dev/disk/by-id"; opendir(IDS, $id_dir); foreach my $id (readdir(IDS)) { local $id_link = readlink("$id_dir/$id"); if ($id_link) { local $id_real = &simplify_path(&resolve_links("$id_dir/$id")); $id_map{$id_real} = $id; } } closedir(IDS); # Call fdisk to get partition and geometry information local $devs = join(" ", @devs); local ($disk, $m2); if ($has_parted) { open(FDISK, join(" ; ", map { "parted $_ unit cyl print 2>/dev/null || ". "fdisk -l $_ 2>/dev/null" } @devs)." |"); } else { open(FDISK, "fdisk -l -u=cylinders $devs 2>/dev/null || fdisk -l $devs 2>/dev/null |"); } while() { if (/Disk\s+([^ :]+):\s+(\d+)\s+\S+\s+(\d+)\s+\S+\s+(\d+)/ || ($m2 = ($_ =~ /Disk\s+([^ :]+):\s+(.*)\s+bytes/)) || ($m3 = ($_ =~ /Disk\s+([^ :]+):\s+([0-9\.]+)cyl/))) { # New disk section if ($m3) { # Parted format $disk = { 'device' => $1, 'prefix' => $1, 'cylinders' => $2 }; } elsif ($m2) { # New style fdisk $disk = { 'device' => $1, 'prefix' => $1, 'table' => 'msdos', }; =~ /(\d+)\s+\S+\s+(\d+)\s+\S+\s+(\d+)/ || next; $disk->{'heads'} = $1; $disk->{'sectors'} = $2; $disk->{'cylinders'} = $3; } else { # Old style fdisk $disk = { 'device' => $1, 'prefix' => $1, 'heads' => $2, 'sectors' => $3, 'cylinders' => $4, 'table' => 'msdos', }; } $disk->{'index'} = scalar(@disks); $disk->{'parts'} = [ ]; local @st = stat($disk->{'device'}); next if (@cdstat && $st[1] == $cdstat[1]); if ($disk->{'device'} =~ /\/sd([a-z]+)$/) { # Old-style SCSI disk $disk->{'desc'} = &text('select_device', 'SCSI', uc($1)); local ($dscsi) = grep { $_->{'dev'} eq "sd$1" } @dscsi; $disk->{'scsi'} = $dscsi ? &indexof($dscsi, @dscsi) : ord(uc($1))-65; $disk->{'type'} = 'scsi'; } elsif ($disk->{'device'} =~ /\/hd([a-z]+)$/) { # IDE disk $disk->{'desc'} = &text('select_device', 'IDE', uc($1)); $disk->{'type'} = 'ide'; } elsif ($disk->{'device'} =~ /\/xvd([a-z]+)$/) { # Xen virtual disk $disk->{'desc'} = &text('select_device', 'Xen', uc($1)); $disk->{'type'} = 'ide'; } elsif ($disk->{'device'} =~ /\/vd([a-z]+)$/) { # KVM virtual disk $disk->{'desc'} = &text('select_device', 'VirtIO', 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"); # Work out the SCSI index for this disk local $j; if ($dscsi_mode) { for($j=0; $j<@dscsi; $j++) { if ($dscsi[$j]->{'host'} == $disk->{'host'} && $dscsi[$j]->{'bus'} == $disk->{'bus'} && $dscsi[$j]->{'target'} == $disk->{'target'} && $dscsi[$j]->{'lnun'} == $disk->{'lun'}) { $disk->{'scsi'} = $j; last; } } } else { 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() { 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() { 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() { 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; } # Work out short name, like sda local $short; if (defined($disk->{'host'})) { $short = &hbt_to_device($disk->{'host'}, $disk->{'bus'}, $disk->{'target'}); } else { $short = $disk->{'device'}; $short =~ s/^.*\///g; } $disk->{'short'} = $short; $disk->{'id'} = $id_map{$disk->{'device'}} || $id_map{"/dev/$short"}; push(@disks, $disk); } elsif (/^Units\s+=\s+cylinders\s+of\s+(\d+)\s+\*\s+(\d+)/) { # Unit size for disk from fdisk $disk->{'bytes'} = $2; $disk->{'cylsize'} = $disk->{'heads'} * $disk->{'sectors'} * $disk->{'bytes'}; $disk->{'size'} = $disk->{'cylinders'} * $disk->{'cylsize'}; } elsif (/BIOS\s+cylinder,head,sector\s+geometry:\s+(\d+),(\d+),(\d+)\.\s+Each\s+cylinder\s+is\s+(\d+)(b|kb|mb)/i) { # Unit size for disk from parted $disk->{'cylinders'} = $1; $disk->{'heads'} = $2; $disk->{'sectors'} = $3; $disk->{'cylsize'} = $4 * (lc($5) eq "b" ? 1 : lc($5) eq "kb" ? 1024 : 1024*1024); $disk->{'bytes'} = $disk->{'cylsize'} / $disk->{'heads'} / $disk->{'sectors'}; $disk->{'size'} = $disk->{'cylinders'} * $disk->{'cylsize'}; } 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 from fdisk local $part = { 'number' => $2, 'device' => $1, 'type' => $6, 'start' => $3, 'end' => $4, 'blocks' => int($5), 'extended' => $6 eq '5' || $6 eq 'f' ? 1 : 0, 'index' => scalar(@{$disk->{'parts'}}), 'edittype' => 1, }; $part->{'desc'} = &partition_description($part->{'device'}); $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * $disk->{'cylsize'}; push(@{$disk->{'parts'}}, $part); } elsif (/^\s*(\d+)\s+(\d+)cyl\s+(\d+)cyl\s+(\d+)cyl\s+(primary|logical|extended)\s*(\S*)\s*(\S*)/) { # Partition within the current disk from parted (msdos format) local $part = { 'number' => $1, 'device' => $disk->{'device'}.$1, 'type' => $6 || 'ext2', 'start' => $2+1, 'end' => $3+1, 'blocks' => $4 * $disk->{'cylsize'}, 'extended' => $5 eq 'extended' ? 1 : 0, 'raid' => $7 eq 'raid' ? 1 : 0, 'index' => scalar(@{$disk->{'parts'}}), 'edittype' => 0, }; $part->{'type'} = 'ext2' if ($part->{'type'} =~ /^ext/); $part->{'type'} = 'raid' if ($part->{'type'} eq 'ext2' && $part->{'raid'}); $part->{'desc'} = &partition_description($part->{'device'}); $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * $disk->{'cylsize'}; push(@{$disk->{'parts'}}, $part); } elsif (/^\s*(\d+)\s+(\d+)cyl\s+(\d+)cyl\s+(\d+)cyl\s(.*)/) { # Partition within the current disk from parted (gpt format) local $part = { 'number' => $1, 'device' => $disk->{'device'}.$1, 'start' => $2+1, 'end' => $3+1, 'blocks' => $4 * $disk->{'cylsize'}, 'extended' => 0, 'index' => scalar(@{$disk->{'parts'}}), 'edittype' => 0, }; # Work out partition type, name and flags local $rest = $5; $rest =~ s/^\s+//; $rest =~ s/,//g; # Remove commas in flags list local @rest = split(/\s+/, $rest); # If first word is a known partition type, assume it is the type if (@rest && &conv_type($rest[0])) { $part->{'type'} = shift(@rest); } # Remove flag words from the end local %flags; while(@rest && $rest[$#rest] =~ /boot|lba|root|swap|hidden|raid|LVM/i) { $flags{lc(pop(@rest))} = 1; } # Anything left in the middle should be the name if (@rest) { $part->{'name'} = $rest[0]; } if ($flags{'raid'}) { # RAID flag is set $part->{'raid'} = 1; } $part->{'type'} = 'ext2' if (!$part->{'type'} || $part->{'type'} =~ /^ext/); $part->{'type'} = 'raid' if ($part->{'type'} eq 'ext2' && $part->{'raid'}); $part->{'desc'} = &partition_description($part->{'device'}); $part->{'size'} = ($part->{'end'} - $part->{'start'} + 1) * $disk->{'cylsize'}; push(@{$disk->{'parts'}}, $part); } elsif (/Partition\s+Table:\s+(\S+)/) { # Parted partition table type $disk->{'table'} = $1; } } close(FDISK); # Check /proc/ide for IDE disk models foreach $d (@disks) { if ($d->{'type'} eq 'ide') { local $short = $d->{'short'}; $d->{'model'} = &read_file_contents("/proc/ide/$short/model"); $d->{'model'} =~ s/\r|\n//g; $d->{'media'} = &read_file_contents("/proc/ide/$short/media"); $d->{'media'} =~ s/\r|\n//g; if ($d->{'short'} =~ /^vd/ && !$d->{'model'}) { # Fake up model for KVM VirtIO disks $d->{'model'} = "KVM VirtIO"; } } } # Fill in SCSI information foreach $d (@disks) { if ($d->{'type'} eq 'scsi') { local $s = $d->{'scsi'}; local $sysdir = "/sys/block/$d->{'short'}/device"; if (-d $sysdir) { # From kernel 2.6.30+ sys directory $d->{'model'} = &read_file_contents("$sysdir/vendor"). " ". &read_file_contents("$sysdir/model"); $d->{'model'} =~ s/\r|\n//g; $d->{'media'} = &read_file_contents("$sysdir/media"); $d->{'media'} =~ s/\r|\n//g; } elsif ($dscsi_mode) { # From other scsi files $d->{'model'} = "$dscsi[$s]->{'make'} $dscsi[$s]->{'model'}"; $d->{'controller'} = $dscsi[$s]->{'host'}; $d->{'scsiid'} = $dscsi[$s]->{'target'}; } else { # From /proc/scsi/scsi lines 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); } } if ($d->{'model'} =~ /ATA/) { # Fake SCSI disk, actually IDE $d->{'scsi'} = 0; $d->{'desc'} =~ s/SCSI/SATA/g; foreach my $p (@{$d->{'parts'}}) { $p->{'desc'} =~ s/SCSI/SATA/g; } } } } @list_disks_partitions_cache = @disks; return @disks; } # partition_description(device) # Converts a device path like /dev/hda into a human-readable name sub partition_description { my ($device) = @_; return $device =~ /(s|h|xv|v)d([a-z]+)(\d+)$/ ? &text('select_part', $1 eq 's' ? 'SCSI' : $1 eq 'xv' ? 'Xen' : $1 eq 'v' ? 'VirtIO' : 'IDE', uc($2), "$3") : $device =~ /scsi\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ? &text('select_spart', "$1", "$2", "$3", "$4", "$5") : $device =~ /ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/ ? &text('select_snewide', "$1", "$2", "$3", "$4", "$5") : $device =~ /rd\/c(\d+)d(\d+)p(\d+)$/ ? &text('select_mpart', "$1", "$2", "$3") : $device =~ /ida\/c(\d+)d(\d+)p(\d+)$/ ? &text('select_cpart', "$1", "$2", "$3") : $device =~ /cciss\/c(\d+)d(\d+)p(\d+)$/ ? &text('select_smartpart', "$1", "$2", "$3") : $device =~ /ataraid\/disc(\d+)\/part(\d+)$/ ? &text('select_ppart', "$1", "$2") : "???"; } # hbt_to_device(host, bus, target) # Converts an IDE device specified as a host, bus and target to an hdX device sub hbt_to_device { local ($host, $bus, $target) = @_; local $num = $host*4 + $bus*2 + $target; return &number_to_device("hd", $num); } # number_to_device(suffix, number) sub number_to_device { local ($suffix, $num) = @_; if ($num < 26) { # Just a single letter return $suffix.(('a' .. 'z')[$num]); } else { # Two-letter format local $first = int($num / 26); local $second = $num % 26; return $suffix.(('a' .. 'z')[$first]).(('a' .. 'z')[$second]); } } # change_type(disk, partition, type) # Changes the type of an existing partition sub change_type { &open_fdisk("$_[0]"); &wprint("t\n"); local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition'); &wprint("$_[1]\n") if ($rv == 0); &wait_for($fh, 'Hex.*:'); &wprint("$_[2]\n"); &wait_for($fh, 'Command.*:'); &wprint("w\n"); sleep(1); &close_fdisk(); undef(@list_disks_partitions_cache); } # delete_partition(disk, partition) # Delete an existing partition sub delete_partition { my ($disk, $part) = @_; if ($has_parted) { # Using parted my $cmd = "parted -s ".$disk." rm ".$part; my $out = &backquote_logged("$cmd &1"); if ($?) { &error("$cmd failed : $out"); } } else { # Using fdisk &open_fdisk($disk); &wprint("d\n"); local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition'); &wprint("$part\n") if ($rv == 0); &wait_for($fh, 'Command.*:'); &wprint("w\n"); &wait_for($fh, 'Syncing'); sleep(3); &close_fdisk(); } undef(@list_disks_partitions_cache); } # create_partition(disk, partition, start, end, type) # Create a new partition with the given extent and type sub create_partition { my ($disk, $part, $start, $end, $type) = @_; if ($has_parted) { # Using parted my $pe = $part > 4 ? "logical" : "primary"; my $cmd; if ($type eq "raid") { $cmd = "parted -s ".$disk." unit cyl mkpartfs ".$pe." ". "ext2 ".($start-1)." ".$end; $cmd .= " ; parted -s ".$disk." set $part raid on"; } elsif ($type && $type ne 'ext2') { $cmd = "parted -s ".$disk." unit cyl mkpartfs ".$pe." ". $type." ".($start-1)." ".$end; } else { $cmd = "parted -s ".$disk." unit cyl mkpart ".$pe." ". ($start-1)." ".$end; } my $out = &backquote_logged("$cmd &1"); if ($?) { &error("$cmd failed : $out"); } } else { # Using fdisk &open_fdisk($disk); &wprint("n\n"); local $wf = &wait_for($fh, 'primary.*\r?\n', 'First.*:'); if ($part > 4) { &wprint("l\n"); } else { &wprint("p\n"); local $wf2 = &wait_for($fh, 'Partition.*:', 'Selected partition'); &wprint("$part\n") if ($wf2 == 0); } &wait_for($fh, 'First.*:') if ($wf != 1); &wprint("$start\n"); &wait_for($fh, 'Last.*:'); &wprint("$end\n"); &wait_for($fh, 'Command.*:'); &wprint("t\n"); local $rv = &wait_for($fh, 'Partition.*:', 'Selected partition'); &wprint("$part\n") if ($rv == 0); &wait_for($fh, 'Hex.*:'); &wprint("$type\n"); &wait_for($fh, 'Command.*:'); &wprint("w\n"); &wait_for($fh, 'Syncing'); sleep(3); &close_fdisk(); } undef(@list_disks_partitions_cache); } # create_extended(disk, partition, start, end) # Create a new extended partition sub create_extended { my ($disk, $part, $start, $end) = @_; if ($has_parted) { # Create using parted my $cmd = "parted -s ".$disk." unit cyl mkpart extended ". ($start-1)." ".$end; my $out = &backquote_logged("$cmd &1"); if ($?) { &error("$cmd failed : $out"); } } else { # Use classic fdisk &open_fdisk($disk); &wprint("n\n"); &wait_for($fh, 'primary.*\r?\n'); &wprint("e\n"); &wait_for($fh, 'Partition.*:'); &wprint("$part\n"); &wait_for($fh, 'First.*:'); &wprint("$start\n"); &wait_for($fh, 'Last.*:'); &wprint("$end\n"); &wait_for($fh, 'Command.*:'); &wprint("w\n"); &wait_for($fh, 'Syncing'); sleep(3); &close_fdisk(); } undef(@list_disks_partitions_cache); } # list_tags() # Returns a list of known partition tag numbers sub list_tags { if ($has_parted) { # Parted types return sort { $a cmp $b } (keys %parted_tags); } else { # Classic fdisk types 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]} || $parted_tags{$_[0]} || $hidden_tags{$_[0]}; } sub default_tag { return $has_parted ? 'ext2' : '83'; } # conv_type(tag) # Given a partition tag, returns the filesystem type (assuming it is supported) sub conv_type { my ($tag) = @_; my @rv; if ($has_parted) { # Use parted type names if ($tag eq "fat16") { @rv = ( "msdos" ); } elsif ($tag eq "fat32") { @rv = ( "vfat" ); } elsif ($tag =~ /^ext/ || $tag eq "raid") { @rv = ( "ext3", "ext4", "ext2", "xfs", "reiserfs" ); } elsif ($tag eq "hfs" || $tag eq "HFS") { @rv = ( "hfs" ); } elsif ($tag eq "linux-swap") { @rv = ( "swap" ); } elsif ($tag eq "NTFS") { @rv = ( "ntfs" ); } elsif ($tag eq "reiserfs") { @rv = "reiserfs"; } elsif ($tag eq "ufs") { @rv = ( "ufs" ); } else { return ( ); } } else { # Use fdisk type IDs if ($tag eq "4" || $tag eq "6" || $tag eq "1" || $tag eq "e") { @rv = ( "msdos" ); } elsif ($tag eq "b" || $tag eq "c") { @rv = ( "vfat" ); } elsif ($tag eq "83") { @rv = ( "ext3", "ext4", "ext2", "xfs", "reiserfs" ); } elsif ($tag eq "82") { @rv = ( "swap" ); } elsif ($tag eq "81") { @rv = ( "minix" ); } else { return ( ); } } local %supp = map { $_, 1 } &mount::list_fstypes(); @rv = grep { $supp{$_} } @rv; return wantarray ? @rv : $rv[0]; } # 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 &ui_table_row($text{'ext2_c'}, &ui_yesno_radio("ext2_c", 0)); } elsif ($_[0] eq "msdos" || $_[0] eq "vfat") { &opt_input("msdos_ff", "", 1); print &ui_table_row($text{'msdos_F'}, &ui_select("msdos_F", undef, [ [ undef, $text{'default'} ], [ 12 ], [ 16 ], [ 32 ], [ "*", $text{'msdos_F_other'} ] ])." ". &ui_textbox("msdos_F_other", undef, 4)); &opt_input("msdos_i", "", 1); &opt_input("msdos_n", "", 0); &opt_input("msdos_r", "", 1); &opt_input("msdos_s", "sectors", 0); print &ui_table_row($text{'msdos_c'}, &ui_yesno_radio("msdos_c", 0)); } elsif ($_[0] eq "minix") { &opt_input("minix_n", "", 1); &opt_input("minix_i", "", 0); &opt_input("minix_b", "", 1); print &ui_table_row($text{'minix_c'}, &ui_yesno_radio("minix_c", 0)); } elsif ($_[0] eq "reiserfs") { print &ui_table_row($text{'reiserfs_force'}, &ui_yesno_radio("reiserfs_f", 0)); print &ui_table_row($text{'reiserfs_hash'}, &ui_select("reiserfs_h", "", [ [ "", $text{'default'} ], [ "rupasov", "tea" ] ])); } elsif ($_[0] =~ /^ext\d+$/) { &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 &ui_table_row($text{'ext2_c'}, &ui_yesno_radio("ext2_c", 0)); } elsif ($_[0] eq "xfs") { print &ui_table_row($text{'xfs_force'}, &ui_yesno_radio("xfs_f", 0)); &opt_input("xfs_b", $text{'bytes'}, 0); } elsif ($_[0] eq "jfs") { &opt_input("jfs_s", $text{'megabytes'}, 1); print &ui_table_row($text{'jfs_c'}, &ui_yesno_radio("jfs_c", 0)); } elsif ($_[0] eq "fatx") { # Has no options! print &ui_table_row(undef, $text{'fatx_none'}, 4); } } # 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 .= " -q"; $cmd .= " $_[1]"; } elsif ($_[0] eq "msdos" || $_[0] eq "vfat") { $cmd = "mkfs -t $_[0]"; $cmd .= &opt_check("msdos_ff", '[1-2]', "-f"); if ($in{'msdos_F'} eq '*') { $in{'msdos_F_other'} =~ /^\d+$/ || &error(&text('opt_error', $in{'msdos_F_other'}, $text{'msdos_F'})); $cmd .= " -F ".$in{'msdos_F_other'}; } elsif ($in{'msdos_F'}) { $cmd .= " -F ".$in{'msdos_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] =~ /^ext\d+$/) { if (&has_command("mkfs.$_[0]")) { $cmd = "mkfs -t $_[0]"; $cmd .= &opt_check("ext3_j", '\d+', "-j"); } elsif ($_[0] eq "ext3" && &has_command("mke3fs")) { $cmd = "mke3fs"; $cmd .= &opt_check("ext3_j", '\d+', "-j"); } elsif ($_[0] eq "ext4" && &has_command("mke4fs")) { $cmd = "mke4fs"; $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 .= " -q"; $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]"; } elsif ($_[0] eq "fatx") { $cmd = "mkfs -t $_[0] $_[1]"; } if (&has_command("partprobe")) { $cmd = "partprobe ; $cmd"; } return $cmd; } # can_tune(type) # Returns 1 if this filesystem type can be tuned sub can_tune { return $_[0] =~ /^ext\d+$/; } # tunefs_options(type) # Output HTML for tuning options for some filesystem type sub tunefs_options { if ($_[0] =~ /^ext\d+$/) { # Gaps between checks &opt_input("tunefs_c", "", 1); # Action on error print &ui_table_row($text{'tunefs_e'}, &ui_radio("tunefs_e_def", 1, [ [ 1, $text{'opt_default'} ], [ 0, &ui_select("tunefs_e", undef, [ [ "continue", $text{'tunefs_continue'} ], [ "remount-ro", $text{'tunefs_remount'} ], [ "panic", $text{'tunefs_panic'} ] ]) ] ])); # Reserved user print &ui_table_row($text{'tunefs_u'}, &ui_opt_textbox("tunefs_u", undef, 13, $text{'opt_default'})." ". &user_chooser_button("tunefs_u", 0)); # Reserved group print &ui_table_row($text{'tunefs_g'}, &ui_opt_textbox("tunefs_g", undef, 13, $text{'opt_default'})." ". &group_chooser_button("tunefs_g", 0)); # Reserved blocks &opt_input("tunefs_m", "%", 1); # Time between checks $tsel = &ui_select("tunefs_i_unit", undef, [ [ "d", $text{'tunefs_days'} ], [ "w", $text{'tunefs_weeks'} ], [ "m", $text{'tunefs_months'} ] ]); &opt_input("tunefs_i", $tsel, 0); } } # tunefs_parse(type, device) # Returns the tuning command based on user inputs sub tunefs_parse { if ($_[0] =~ /^ext\d+$/) { $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, module 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 $volid = &get_volid($_[0]); local ($mounted) = grep { &same_file($_->[1], $_[0]) || $_->[1] eq "LABEL=$label" || $_->[1] eq "UUID=$volid" } @mounted; local ($mount) = grep { &same_file($_->[1], $_[0]) || $_->[1] eq "LABEL=$label" || $_->[1] eq "UUID=$volid" } @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) { my $raidconf = &foreign_call("raid", "get_raidtab") if (!$raidconf); foreach $c (@$raidconf) { foreach $d (&raid::find_value('device', $c->{'members'})) { return ( $c->{'value'}, "raid", 1, "raid" ) if ($d eq $_[0]); } } } if ($lvm_module) { if (!scalar(@physical_volumes)) { @physical_volumes = (); foreach $vg (&foreign_call("lvm", "list_volume_groups")) { push(@physical_volumes, &foreign_call("lvm", "list_physical_volumes", $vg->{'name'})); } } foreach my $pv (@physical_volumes) { return ( $pv->{'vg'}, "lvm", 1, "lvm") if ($pv->{'device'} eq $_[0]); } } if ($iscsi_server_module) { my $iscsiconf = &iscsi_server::get_iscsi_config(); foreach my $c (@$iscsiconf) { if ($c->{'type'} eq 'extent' && $c->{'device'} eq $_[0]) { return ( $c->{'type'}.$c->{'num'}, "iscsi", 1, "iscsi-server"); } } } if ($iscsi_target_module) { my $iscsiconf = &iscsi_target::get_iscsi_config(); foreach my $t (&iscsi_target::find($iscsiconf, "Target")) { foreach my $l (&iscsi_target::find($t->{'members'}, "Lun")) { if ($l->{'value'} =~ /Path=([^, ]+)/ && $1 eq $_[0]) { return ( $t->{'value'}, "iscsi", 1, "iscsi-target"); } } } } return (); } # can_fsck(type) # Returns 1 if some filesystem type can fsck'd sub can_fsck { return ($_[0] =~ /^ext\d+$/ && &has_command("fsck.$_[0]") || $_[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] =~ /^ext\d+$/) { return "fsck -t $_[0] -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 ($name, $value, $mode, $found, $diskre) = @_; local $rv = "