Add files via upload

gpart edition
This commit is contained in:
karmantyu
2026-01-23 19:41:57 +01:00
committed by GitHub
parent e9586fb2d8
commit 032f4447db
16 changed files with 2623 additions and 946 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,10 @@ $slice || &error($text{'slice_egone'});
# Validate inputs, starting with slice number
my $part = { };
$in{'letter'} =~ /^[a-d]$/i || &error($text{'npart_eletter'});
$in{'letter'} =~ /^[a-h]$/i || &error($text{'npart_eletter'});
$in{'letter'} = lc($in{'letter'});
# Partition 'c' is reserved in BSD disklabels (represents the whole slice)
$in{'letter'} ne 'c' || &error($text{'npart_ereserved'});
my ($clash) = grep { $_->{'letter'} eq $in{'letter'} } @{$slice->{'parts'}};
$clash && &error(&text('npart_eclash', $in{'letter'}));
$part->{'letter'} = $in{'letter'};
@@ -30,7 +32,7 @@ $in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
$in{'start'} < $in{'end'} || &error($text{'npart_erange'});
$part->{'startblock'} = $in{'start'};
$part->{'blocks'} = $in{'end'} - $in{'start'};
$part->{'blocks'} = $in{'end'} - $in{'start'} + 1;
# Slice type
$part->{'type'} = $in{'type'};
@@ -39,7 +41,8 @@ $part->{'type'} = $in{'type'};
&ui_print_header($slice->{'desc'}, $text{'npart_title'}, "");
print &text('npart_creating', $in{'letter'}, $slice->{'desc'}),"<p>\n";
my $err = &save_partition($disk, $slice, $part);
# Actually create the partition inside the slice (initialize BSD label if needed)
my $err = &create_partition($disk, $slice, $part);
if ($err) {
print &text('npart_failed', $err),"<p>\n";
}

View File

@@ -1,65 +1,109 @@
#!/usr/local/bin/perl
# Actually create a new slice
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
&error_setup($text{'nslice_err'});
# Get the disk
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
ReadParse();
error_setup($text{'nslice_err'});
# Get the disk using first() for an early exit on match
my @disks = list_disks_partitions();
my $disk;
foreach my $d (@disks) {
if ($d->{'device'} eq $in{'device'}) {
$disk = $d;
last;
}
}
# Validate device parameter to prevent path traversal and command injection
$disk or error($text{'disk_egone'});
# Prefer GPART total blocks for bounds
(my $base_dev = $in{'device'}) =~ s{^/dev/}{};
my $ds = get_disk_structure($base_dev);
my $disk_blocks = ($ds && $ds->{'total_blocks'}) ? $ds->{'total_blocks'} : ($disk->{'blocks'} || 0);
# Validate inputs, starting with slice number
my $slice = { };
$in{'number'} =~ /^\d+$/ || &error($text{'nslice_enumber'});
my ($clash) = grep { $_->{'number'} == $in{'number'} } @{$disk->{'slices'}};
$clash && &error(&text('nslice_eclash', $in{'number'}));
my $slice = {};
$in{'number'} =~ /^\d+$/ or error($text{'nslice_enumber'});
# Check for clash using first() with a loop exiting on first match
my $clash;
foreach my $s (@{$disk->{'slices'}}) {
if ($s->{'number'} == $in{'number'}) {
$clash = $s;
last;
}
}
$slice->{'number'} = $in{'number'};
# Start and end blocks
$in{'start'} =~ /^\d+$/ || &error($text{'nslice_estart'});
$in{'end'} =~ /^\d+$/ || &error($text{'nslice_eend'});
$in{'start'} < $in{'end'} || &error($text{'nslice_erange'});
$in{'start'} =~ /^\d+$/ or error($text{'nslice_estart'});
$in{'end'} =~ /^\d+$/ or error($text{'nslice_eend'});
($in{'start'} < $in{'end'}) or error($text{'nslice_erange'});
# total_blocks is the block *after* the last valid block, so end must be < total_blocks
($in{'end'} < $disk_blocks) or error(text('nslice_emax', $disk_blocks - 1));
# Ensure the new slice does not overlap existing slices
foreach my $s (@{ $disk->{'slices'} }) {
my $s_start = $s->{'startblock'};
my $s_end = $s->{'startblock'} + $s->{'blocks'} - 1;
if (!($in{'end'} < $s_start || $in{'start'} > $s_end)) {
error("Requested slice range overlaps with existing slice #".$s->{'number'});
}
}
$slice->{'startblock'} = $in{'start'};
$slice->{'blocks'} = $in{'end'} - $in{'start'};
# Slice type
$slice->{'blocks'} = $in{'end'} - $in{'start'} + 1;
# Slice type
$in{'type'} =~ /^[a-zA-Z0-9_-]+$/ or error($text{'nslice_etype'});
length($in{'type'}) <= 20 or error($text{'nslice_etype'});
$slice->{'type'} = $in{'type'};
# Do the creation
&ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
print &text('nslice_creating', $in{'number'}, $disk->{'desc'}),"<p>\n";
my $err = &create_slice($disk, $slice);
ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
print text('nslice_creating', $in{'number'}, $disk->{'desc'}), "<p>\n";
my $err = create_slice($disk, $slice);
if ($err) {
print &text('nslice_failed', $err),"<p>\n";
}
else {
print &text('nslice_done'),"<p>\n";
}
print text('nslice_failed', $err), "<p>\n";
} else {
print text('nslice_done'), "<p>\n";
# Auto-label the new partition provider with its name if scheme is GPT or BSD
my $base = $disk->{'device'}; $base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ($ds && $ds->{'scheme'}) {
# Determine provider and label text
my $label_text = slice_name($slice); # e.g., da8s2 or da0p2
if ($ds->{'scheme'} =~ /GPT/i) {
my $idx = $slice->{'number'};
if ($idx) {
my $cmd2 = "gpart modify -i $idx -l " . quote_path($label_text) . " $base";
my $out2 = `$cmd2 2>&1`;
# If it fails, ignore silently
}
} else {
# On MBR, if BSD label exists we can set label once created; ignore for now
}
}
}
if (!$err && $in{'makepart'}) {
# Also create a partition
print &text('nslice_parting', $in{'number'}, $disk->{'desc'}),"<p>\n";
my $err = &initialize_slice($disk, $slice);
if ($err) {
print &text('nslice_pfailed', $err),"<p>\n";
}
else {
print &text('nslice_pdone'),"<p>\n";
}
}
# Also create a partition (initialize slice label)
my $part_err = initialize_slice($disk, $slice);
if ($part_err) {
print text('nslice_pfailed', $part_err), "<p>\n";
} else {
print text('nslice_pdone'), "<p>\n";
}
}
if (!$err) {
&webmin_log("create", "slice", $slice->{'device'}, $slice);
}
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
# Auto-label GPT partitions with their device name (e.g., da8p2)
my $base = $disk->{'device'}; $base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if ($ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i) {
my $slice_devname = $slice->{'device'};
$slice_devname =~ s{^/dev/}{}; # e.g., da8p2
my $idx = $slice->{'number'};
if ($idx && $slice_devname) {
my $label_cmd = "gpart modify -i $idx -l " . quote_path($slice_devname) . " $base 2>&1";
my $label_out = `$label_cmd`;
# Ignore errors - labeling is optional
}
}
webmin_log("create", "slice", $slice->{'device'}, $slice);
}
ui_print_footer("edit_disk.cgi?device=$in{'device'}", $text{'disk_return'});

View File

@@ -1,6 +1,5 @@
#!/usr/local/bin/perl
# Show details of a disk, and slices on it
use strict;
use warnings;
no warnings 'redefine';
@@ -8,93 +7,334 @@ no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
my $extwidth = 300;
my $extwidth = 100;
# Get the disk
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
# Cache commonly used values
my $device = $disk->{'device'};
my $device_url = &urlize($device);
my $desc = $disk->{'desc'};
# Prefer total blocks from gpart header when available
my $base_device = $disk->{'device'}; $base_device =~ s{^/dev/}{};
my $disk_structure = &get_disk_structure($base_device);
my $disk_blocks = ($disk_structure && $disk_structure->{'total_blocks'}) ? $disk_structure->{'total_blocks'} : ($disk->{'blocks'} || 1000000);
# Precompute a scale factor for extent image widths
my $scale = $extwidth / ($disk_blocks || 1);
&ui_print_header($disk->{'desc'}, $text{'disk_title'}, "");
# Show disk details
my @info = ( );
push(@info, &text('disk_dsize', &nice_size($disk->{'size'})));
# Debug toggle bar
print "<div class='debug-toggle' style='margin-bottom: 15px; text-align: right;'>";
if ($in{'debug'}) {
print "<a href='edit_disk.cgi?device=$device_url' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_hide_debug'}</a>";
} else {
print "<a href='edit_disk.cgi?device=$device_url&debug=1' class='btn btn-default'><i class='fa fa-bug'></i> $text{'disk_show_debug'}</a>";
}
print "</div>";
# Get detailed disk information from geom and disk structure from gpart show (cache disk_structure entries)
my $geom_info = &get_detailed_disk_info($device);
my $entries = $disk_structure && $disk_structure->{'entries'} ? $disk_structure->{'entries'} : [];
print &ui_table_start($text{'disk_details'}, "width=100%", 2);
# Prefer mediasize (bytes) for accurate size; fallback to stat-based size
my $disk_bytes = ($disk_structure && $disk_structure->{'mediasize'}) ? $disk_structure->{'mediasize'} : $disk->{'size'};
print &ui_table_row($text{'disk_dsize'}, &safe_nice_size($disk_bytes));
if ($disk->{'model'}) {
push(@info, &text('disk_model', $disk->{'model'}));
print &ui_table_row($text{'disk_model'}, $disk->{'model'});
}
print &ui_table_row($text{'disk_device'}, "<tt>$disk->{'device'}</tt>");
# Get disk scheme
print &ui_table_row($text{'disk_scheme'}, $disk_structure ? $disk_structure->{'scheme'} : $text{'disk_unknown'});
# GEOM details
if ($geom_info) {
print &ui_table_hr();
print &ui_table_row($text{'disk_geom_header'}, "<b>$text{'disk_geom_details'}</b>", 2);
if ($geom_info->{'mediasize'}) {
print &ui_table_row($text{'disk_mediasize'}, $geom_info->{'mediasize'});
}
if ($geom_info->{'sectorsize'}) {
print &ui_table_row($text{'disk_sectorsize'}, $geom_info->{'sectorsize'} . " " . $text{'disk_bytes'});
}
if ($geom_info->{'stripesize'}) {
print &ui_table_row($text{'disk_stripesize'}, $geom_info->{'stripesize'} . " " . $text{'disk_bytes'});
}
if ($geom_info->{'stripeoffset'}) {
print &ui_table_row($text{'disk_stripeoffset'}, $geom_info->{'stripeoffset'} . " " . $text{'disk_bytes'});
}
if ($geom_info->{'mode'}) {
print &ui_table_row($text{'disk_mode'}, $geom_info->{'mode'});
}
if ($geom_info->{'rotationrate'}) {
if ($geom_info->{'rotationrate'} eq "0") {
print &ui_table_row($text{'disk_rotationrate'}, $text{'disk_ssd'});
} else {
print &ui_table_row($text{'disk_rotationrate'}, $geom_info->{'rotationrate'} . " " . $text{'disk_rpm'});
}
push(@info, &text('disk_cylinders', $disk->{'cylinders'}));
push(@info, &text('disk_blocks', $disk->{'blocks'}));
push(@info, &text('disk_device', "<tt>$disk->{'device'}</tt>"));
print &ui_links_row(\@info),"<p>\n";
}
if ($geom_info->{'ident'}) {
print &ui_table_row($text{'disk_ident'}, $geom_info->{'ident'});
}
if ($geom_info->{'lunid'}) {
print &ui_table_row($text{'disk_lunid'}, $geom_info->{'lunid'});
}
if ($geom_info->{'descr'}) {
print &ui_table_row($text{'disk_descr'}, $geom_info->{'descr'});
}
}
# Advanced information (cylinders, blocks)
print &ui_table_hr();
print &ui_table_row($text{'disk_advanced_header'}, "<b>$text{'disk_advanced_details'}</b>", 2);
if ($disk->{'cylinders'}) {
print &ui_table_row($text{'disk_cylinders'}, $disk->{'cylinders'});
}
print &ui_table_row($text{'disk_blocks'}, $disk->{'blocks'});
print &ui_table_end();
# Debug: print raw outputs if debug mode is enabled
if ($in{'debug'}) {
print "<div class='debug-section'>";
# Debug: gpart show output
my $cmd = "gpart show -l $base_device 2>&1";
my $out = &backquote_command($cmd);
print "<div class='panel panel-default'>";
print "<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_gpart'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Command: $cmd\nOutput:\n$out\n</pre>";
print "</div></div>";
# Debug: disk structure
print "<div class='panel panel-default'>";
print "<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_structure'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Disk Structure:\n";
foreach my $key (sort keys %$disk_structure) {
if ($key eq 'entries') {
print "entries: [\n";
foreach my $entry (@{$disk_structure->{'entries'}}) {
print " {\n";
foreach my $k (sort keys %$entry) {
print " $k: $entry->{$k}\n";
}
print " },\n";
}
print "]\n";
} else {
print "$key: $disk_structure->{$key}\n";
}
}
print "</pre>";
print "</div></div>";
# Debug: Raw GEOM output
print "<div class='panel panel-default'>";
print "<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_geom'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Raw GEOM output:\n";
print &html_escape(&backquote_command("geom disk list " . &quote_path($device) . " 2>/dev/null"));
print "</pre>";
print "</div></div>";
print "</div>";
}
# Build partition details from disk_structure (no separate gpart list call)
my %part_details = ();
if ($disk_structure && $disk_structure->{'partitions'}) {
%part_details = %{ $disk_structure->{'partitions'} };
}
# Ensure we have names/labels for any entries missing from partitions map
if ($disk_structure && $disk_structure->{'entries'}) {
foreach my $entry (@{$disk_structure->{'entries'}}) {
next unless ($entry->{'type'} eq 'partition' && $entry->{'index'});
my $part_num = $entry->{'index'};
$part_details{$part_num} ||= {};
$part_details{$part_num}->{'name'} ||= $base_device . (($disk_structure->{'scheme'} eq 'GPT') ? "p$part_num" : "s$part_num");
if ($entry->{'label'} && $entry->{'label'} ne '(null)') {
$part_details{$part_num}->{'label'} ||= $entry->{'label'};
}
$part_details{$part_num}->{'type'} ||= $entry->{'part_type'} || 'unknown';
}
}
# Build ZFS devices cache
my ($zfs_pools, $zfs_devices) = &build_zfs_devices_cache();
# Debug ZFS pools if debug mode is enabled
if ($in{'debug'}) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print "<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>";
my $cmd = "zpool status 2>&1";
my $out = &backquote_command($cmd);
print "Command: $cmd\nOutput:\n$out\n";
print "</pre>";
print "</div></div>";
print "</div>";
}
# Debug: Print partition details mapping if debug enabled
if ($in{'debug'}) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print "<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_part_details'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Partition Details Mapping:\n";
foreach my $pnum (sort { $a <=> $b } keys %part_details) {
print " $pnum: {\n";
foreach my $k (sort keys %{$part_details{$pnum}}) {
print " $k: $part_details{$pnum}->{$k}\n";
}
print " },\n";
}
print "</pre>";
print "</div></div>";
print "</div>";
}
# Get sector size
my $sectorsize = $disk_structure->{'sectorsize'} || &get_disk_sectorsize($device) || 512;
my $sectorsize_text = $sectorsize ? "$sectorsize" : "512";
# Show partitions table
my @links = ( "<a href='slice_form.cgi?device=".&urlize($disk->{'device'}).
"&new=1'>".$text{'disk_add'}."</a>" );
if (@{$disk->{'slices'}}) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'disk_no'},
$text{'disk_type'},
$text{'disk_extent'},
$text{'disk_size'},
$text{'disk_start'},
$text{'disk_end'},
$text{'disk_use'},
]);
foreach my $p (@{$disk->{'slices'}}) {
# Create images for the extent
my $ext = "";
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($p->{'startblock'} - 1) /
$disk->{'blocks'};
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
$p->{'extended'} ? "ext" : "use",
$extwidth*($p->{'blocks'}) /
$disk->{'blocks'};
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($disk->{'blocks'} - $p->{'startblock'} -
$p->{'blocks'}) / $disk->{'blocks'};
# Work out use
my @st = &fdisk::device_status($p->{'device'});
my $use = &fdisk::device_status_link(@st);
my $n = scalar(@{$p->{'parts'}});
# Add row for the slice
my $url = "edit_slice.cgi?device=".&urlize($disk->{'device'}).
"&slice=".$p->{'number'};
my $nlink = "<a href='$url'>$p->{'number'}</a>";
$nlink = "<b>$nlink</b>" if ($p->{'active'});
print &ui_columns_row([
$nlink,
"<a href='$url'>".&fdisk::tag_name($p->{'type'})."</a>",
$ext,
&nice_size($p->{'size'}),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$use ? $use :
$n ? &text('disk_scount', $n) : "",
]);
}
print &ui_columns_end();
}
else {
print "<b>$text{'disk_none'}</b><p>\n";
}
my @links = ( "<a href='slice_form.cgi?device=$device_url&amp;new=1'>".$text{'disk_add'}."</a>" );
if (@$entries) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'disk_no'}, # Row number
$text{'disk_partno'}, # Part. No.
$text{'disk_partname'}, # Part. Name
$text{'disk_partlabel'}, # Part. Label
$text{'disk_subpart'}, # Sub-part.
$text{'disk_extent'}, # Extent
$text{'disk_start'}, # Startblock
$text{'disk_end'}, # Endblock
$text{'disk_size'}, # Size
$text{'disk_format'}, # Format type
$text{'disk_use'}, # Used by
$text{'disk_role'}, # Role Type
]);
my $row_number = 1;
foreach my $entry (@$entries) {
my @cols = ();
push(@cols, $row_number++);
if ($entry->{'type'} eq 'free') {
my $start = $entry->{'start'};
my $end = $entry->{'start'} + $entry->{'size'} - 1;
my $create_url = "slice_form.cgi?device=$device_url&new=1&start=$start&end=$end";
push(@cols, "<a href='$create_url' style='color: green;'>".$text{'disk_free'}."</a>");
push(@cols, "-");
push(@cols, "-");
push(@cols, "-");
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>", $scale * ($entry->{'start'} - 1);
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d' style='background-color: #8f8;'>", $scale * ($entry->{'size'});
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>", $scale * ($disk_blocks - $entry->{'start'} - $entry->{'size'});
push(@cols, $ext);
push(@cols, $start);
push(@cols, $end);
push(@cols, $entry->{'size_human'});
push(@cols, $text{'disk_free_space'});
push(@cols, $text{'disk_available'});
push(@cols, "-");
} else {
my $part_num = $entry->{'index'};
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>", $scale * ($entry->{'start'} - 1);
$ext .= sprintf "<img src='images/use.gif' height='10' width='%d'>", $scale * ($entry->{'size'});
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>", $scale * ($disk_blocks - $entry->{'start'} - $entry->{'size'});
my $url = "edit_slice.cgi?device=$device_url&amp;slice=".&urlize($part_num);
push(@cols, "<a href='$url'>".&html_escape($part_num)."</a>");
my $part_info = $part_details{$part_num};
my $part_name = $part_info ? $part_info->{'name'} : "-";
push(@cols, $part_name);
my $part_label = $part_info ? $part_info->{'label'} : ($entry->{'label'} eq "(null)" ? "-" : $entry->{'label'});
push(@cols, $part_label);
# Find sub-partitions if available
my ($slice) = grep { $_->{'number'} eq $part_num } @{$disk->{'slices'} || []};
my $sub_part_info = ($slice && scalar(@{$slice->{'parts'}||[]}) > 0) ?
join(", ", map { $_->{'letter'} } @{$slice->{'parts'}}) : "-";
push(@cols, $sub_part_info);
push(@cols, $ext);
push(@cols, $entry->{'start'});
push(@cols, $entry->{'start'} + $entry->{'size'} - 1);
push(@cols, $entry->{'size_human'});
# Classify format/use/role via library helper
my ($format_type, $usage, $role) = classify_partition_row(
base_device => $base_device,
scheme => ($disk_structure->{'scheme'} || ''),
part_num => $part_num,
part_name => $part_name,
part_label => $part_label,
entry_part_type => ($part_info ? $part_info->{'type'} : $entry->{'part_type'}),
entry_rawtype => ($part_info ? $part_info->{'rawtype'} : undef),
size_human => $entry->{'size_human'},
size_blocks => $entry->{'size'},
zfs_devices => $zfs_devices,
);
push(@cols, $format_type || '-');
push(@cols, $usage || $text{'part_nouse'});
push(@cols, $role || '-');
}
print &ui_columns_row(\@cols);
}
print &ui_columns_end();
} else {
if (@{$disk->{'slices'}||[]}) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'disk_no'},
$text{'disk_type'},
$text{'disk_extent'},
$text{'disk_start'},
$text{'disk_end'},
$text{'disk_use'},
]);
foreach my $s (@{$disk->{'slices'}}) {
my @cols = ();
my $ext = "";
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>", $scale * ($s->{'startblock'} - 1);
$ext .= sprintf "<img src='images/%s.gif' height='10' width='%d'>", ($s->{'extended'} ? "ext" : "use"), $scale * ($s->{'blocks'});
$ext .= sprintf "<img src='images/gap.gif' height='10' width='%d'>", $scale * ($disk_blocks - $s->{'startblock'} - $s->{'blocks'});
my $url = "edit_slice.cgi?device=$device_url&amp;slice=".&urlize($s->{'number'});
push(@cols, "<a href='$url'>".&html_escape($s->{'number'})."</a>");
push(@cols, &get_type_description($s->{'type'}) || $s->{'type'});
push(@cols, $ext);
push(@cols, $s->{'startblock'});
push(@cols, $s->{'startblock'} + $s->{'blocks'} - 1);
my @st = &fdisk::device_status($s->{'device'});
my $use = &fdisk::device_status_link(@st);
push(@cols, $use || $text{'part_nouse'});
print &ui_columns_row(\@cols);
}
print &ui_columns_end();
} else {
print "<p>$text{'disk_none'}</p>\n";
}
}
print &ui_links_row(\@links);
print &ui_hr();
print &ui_buttons_start();
if (&foreign_installed("smart-status")) {
print &ui_buttons_row(
"../smart-status/index.cgi",
$text{'disk_smart'},
$text{'disk_smartdesc'},
&ui_hidden("drive", $disk->{'device'}.":"));
}
print &ui_buttons_end();
&ui_print_footer("", $text{'index_return'});
# Show SMART status link if available
if (&has_command("smartctl")) {
print &ui_hr();
print &ui_buttons_start();
print &ui_buttons_row("smart.cgi", $text{'disk_smart'}, $text{'disk_smartdesc'}, &ui_hidden("device", $device));
print &ui_buttons_end();
}
# Debug: ZFS cache detail
if ($in{'debug'}) {
print "<div class='debug-section'>";
print "<div class='panel panel-default'>";
print "<div class='panel-heading'><h3 class='panel-title'>$text{'disk_debug_zfs_cache'}</h3></div>";
print "<div class='panel-body'>";
print "<pre>Pools: " . join(", ", keys %$zfs_pools) . "\n\nDevices:\n";
foreach my $device_id (sort keys %$zfs_devices) {
next if $device_id =~ /^_debug_/;
my $device_info = $zfs_devices->{$device_id};
print "$device_id => Pool: $device_info->{'pool'}, Type: $device_info->{'vdev_type'}, Mirrored: " .
($device_info->{'is_mirrored'} ? "Yes" : "No") . ", RAIDZ: " .
($device_info->{'is_raidz'} ? "Yes (Level: $device_info->{'raidz_level'})" : "No") .
", Single: " . ($device_info->{'is_single'} ? "Yes" : "No") .
", Striped: " . ($device_info->{'is_striped'} ? "Yes" : "No") . "\n";
}
print "</pre>";
print "</div></div>";
print "</div>";
}
&ui_print_footer("", $text{'disk_return'});

View File

@@ -1,87 +1,126 @@
#!/usr/local/bin/perl
# Show details of a partition, with buttons to create a filesystem
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{$slice->{'parts'}};
$part || &error($text{'part_egone'});
&ui_print_header($part->{'desc'}, $text{'part_title'}, "");
# Load required libraries
require "./bsdfdisk-lib.pl";
our ( %in, %text, $module_name );
ReadParse();
# Cache input parameters to avoid repeated hash lookups
my $device = $in{'device'};
my $slice_num = $in{'slice'};
my $part_letter = $in{'part'};
# Get the disk and slice using first() to stop at the first matching element
my @disks = list_disks_partitions();
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or error($text{'disk_edevice'});
$in{'device'} !~ /\.\./ or error($text{'disk_edevice'});
my $disk;
foreach my $d (@disks) {
if ($d->{'device'} eq $device) {
$disk = $d;
last;
}
}
$disk or error($text{'disk_egone'});
my $slice;
foreach my $s (@{$disk->{'slices'}}) {
if ($s->{'number'} eq $slice_num) {
$slice = $s;
last;
}
}
$slice or error($text{'slice_egone'});
my $part;
foreach my $p (@{$slice->{'parts'}}) {
if ($p->{'letter'} eq $part_letter) { $part = $p; last; }
}
$part or error($text{'part_egone'});
ui_print_header($part->{'desc'}, $text{'part_title'}, "");
# Check if this is a boot partition
my $is_boot = is_boot_partition($part);
if ($is_boot) {
print ui_alert_box($text{'part_bootdesc'}, 'info');
}
# Show current details
my @st = &fdisk::device_status($part->{'device'});
my $use = &fdisk::device_status_link(@st);
my $canedit = !@st || !$st[2];
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'})."\n".
&ui_hidden("part", $in{'part'})."\n";
my $zfs_info = get_all_zfs_info();
my @st = fdisk::device_status($part->{'device'});
# calculate $use from either ZFS info or from a status link
my $device_path = $part->{'device'};
my $use = $zfs_info->{ $device_path } || fdisk::device_status_link(@st);
my $canedit = (!@st && !$zfs_info->{ $device_path } && !$is_boot);
# Prepare hidden fields once
my $hiddens = ui_hidden("device", $device) . "\n" .
ui_hidden("slice", $slice_num) . "\n" .
ui_hidden("part", $part_letter) . "\n";
if ($canedit) {
print &ui_form_start("save_part.cgi", "post");
print $hiddens;
}
print &ui_table_start($text{'part_header'}, undef, 2);
print &ui_table_row($text{'part_device'},
"<tt>$part->{'device'}</tt>");
print &ui_table_row($text{'part_size'},
&nice_size($part->{'size'}));
print &ui_table_row($text{'part_start'},
$part->{'startblock'});
print &ui_table_row($text{'part_end'},
$part->{'startblock'} + $part->{'blocks'} - 1);
print ui_form_start("save_part.cgi", "post"), $hiddens;
}
print ui_table_start($text{'part_header'}, undef, 2);
print ui_table_row($text{'part_device'}, "<tt>$part->{'device'}</tt>");
my $part_bytes = bytes_from_blocks($part->{'device'}, $part->{'blocks'});
print ui_table_row($text{'part_size'}, $part_bytes ? safe_nice_size($part_bytes) : '-');
print ui_table_row($text{'part_start'}, $part->{'startblock'});
print ui_table_row($text{'part_end'}, $part->{'startblock'} + $part->{'blocks'} - 1);
my $disk_geom = get_detailed_disk_info($disk->{'device'});
my $stripesize = ($disk_geom && $disk_geom->{'stripesize'}) ? $disk_geom->{'stripesize'} : '-';
print ui_table_row($text{'disk_stripesize'}, $stripesize);
if ($canedit) {
print &ui_table_row($text{'part_type'},
&ui_select("type", $part->{'type'},
[ &list_partition_types() ], 1, 0, 1));
}
else {
print &ui_table_row($text{'part_type'},
$part->{'type'});
}
print &ui_table_row($text{'part_use'},
!@st ? $text{'part_nouse'} :
$st[2] ? &text('part_inuse', $use) :
&text('part_foruse', $use));
print &ui_table_end();
# BSD disklabel partitions only support FreeBSD types
print ui_table_row($text{'part_type'},
ui_select("type", $part->{'type'}, [ list_partition_types('BSD') ], 1, 0, 1));
} else {
print ui_table_row($text{'part_type'}, get_format_type($part));
}
my $use_text = ((!@st && !$zfs_info->{ $part->{'device'} })
? $text{'part_nouse'}
: (($st[2] || $zfs_info->{ $part->{'device'} })
? text('part_inuse', $use)
: text('part_foruse', $use)));
print ui_table_row($text{'part_use'}, $use_text);
# Add a row for the partition role
print ui_table_row($text{'part_role'}, get_partition_role($part));
print ui_table_end();
if ($canedit) {
print &ui_form_end([ [ undef, $text{'save'} ] ]);
}
print ui_form_end([[ undef, $text{'save'} ]]);
}
# Show newfs and mount buttons
# Existing partitions on this slice
if (@{ $slice->{'parts'} || [] }) {
my $zfs = get_all_zfs_info();
print ui_hr();
print ui_columns_start([
$text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'}, $text{'slice_end'}, $text{'slice_size'}, $text{'slice_use'}, $text{'slice_role'}
], $text{'epart_existing'});
foreach my $p (sort { $a->{'startblock'} <=> $b->{'startblock'} } @{ $slice->{'parts'} }) {
my $ptype = get_type_description($p->{'type'}) || $p->{'type'};
my @stp = fdisk::device_status($p->{'device'});
my $usep = $zfs->{$p->{'device'}} || fdisk::device_status_link(@stp) || $text{'part_nouse'};
my $rolep = get_partition_role($p);
my $pb2 = bytes_from_blocks($p->{'device'}, $p->{'blocks'});
print ui_columns_row([
uc($p->{'letter'}), $ptype, $p->{'startblock'}, $p->{'startblock'} + $p->{'blocks'} - 1, ($pb2 ? safe_nice_size($pb2) : '-'), $usep, $rolep
]);
}
print ui_columns_end();
}
# Show newfs and mount buttons if editing is allowed
if ($canedit) {
print &ui_hr();
print &ui_buttons_start();
&show_filesystem_buttons($hiddens, \@st, $part);
print &ui_buttons_row(
"delete_part.cgi", $text{'part_delete'},
$text{'part_deletedesc'}, $hiddens);
print &ui_buttons_end();
}
else {
print "<b>$text{'part_cannotedit'}</b><p>\n";
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
print ui_hr();
print ui_buttons_start();
show_filesystem_buttons($hiddens, \@st, $part);
print ui_buttons_row("delete_part.cgi", $text{'part_delete'}, $text{'part_deletedesc'}, $hiddens);
print ui_buttons_end();
} else {
print ($is_boot) ? "<b>$text{'part_bootcannotedit'}</b><p>\n"
: "<b>$text{'part_cannotedit'}</b><p>\n";
}
# SMART button (physical device)
if (&has_command("smartctl")) {
print ui_hr();
print ui_buttons_start();
print ui_buttons_row("smart.cgi", $text{'disk_smart'}, $text{'disk_smartdesc'},
ui_hidden("device", $disk->{'device'}));
print ui_buttons_end();
}
ui_print_footer("edit_slice.cgi?device=$device&slice=$slice_num", $text{'slice_return'});

View File

@@ -1,146 +1,178 @@
#!/usr/local/bin/perl
# Show details of a slice, and partitions on it
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
ReadParse();
my $extwidth = 300;
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
&ui_print_header($slice->{'desc'}, $text{'slice_title'}, "");
my @disks = list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks or error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}} or error($text{'slice_egone'});
ui_print_header($slice->{'desc'}, $text{'slice_title'}, "");
# Show slice details
my @st = &fdisk::device_status($slice->{'device'});
my $use = &fdisk::device_status_link(@st);
my $canedit = !@st || !$st[2];
my $hiddens = &ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'})."\n";
print &ui_form_start("save_slice.cgi");
my $zfs_info = get_all_zfs_info();
my ($zfs_pools, $zfs_devices) = build_zfs_devices_cache();
# Cache slice device status
my @slice_status = fdisk::device_status($slice->{'device'});
my $slice_use = $zfs_info->{$slice->{'device'}} ? $zfs_info->{$slice->{'device'}} : fdisk::device_status_link(@slice_status);
my $canedit = (! @slice_status || !$slice_status[2]);
# Prepare hidden fields
my $hiddens = ui_hidden("device", $in{'device'}) . "\n" . ui_hidden("slice", $in{'slice'}) . "\n";
# Derive disk scheme for classifier
my $base_device = $disk->{'device'}; $base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
# Check if this is a boot slice
my $is_boot = is_boot_partition($slice);
print ui_alert_box($text{'slice_bootdesc'}, 'info') if $is_boot;
print ui_form_start("save_slice.cgi");
print $hiddens;
print &ui_table_start($text{'slice_header'}, undef, 2);
print &ui_table_row($text{'part_device'},
"<tt>$slice->{'device'}</tt>");
print &ui_table_row($text{'slice_ssize'},
&nice_size($slice->{'size'}));
print &ui_table_row($text{'slice_sstart'},
$slice->{'startblock'});
print &ui_table_row($text{'slice_send'},
$slice->{'startblock'} + $slice->{'blocks'} - 1);
print &ui_table_row($text{'slice_stype'},
&ui_select("type", $slice->{'type'},
[ sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] }
&fdisk::list_tags() ]));
print &ui_table_row($text{'slice_sactive'},
$slice->{'active'} ? $text{'yes'} :
&ui_yesno_radio("active", $slice->{'active'}));
print &ui_table_row($text{'slice_suse'},
!@st ? $text{'part_nouse'} :
$st[2] ? &text('part_inuse', $use) :
&text('part_foruse', $use));
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
print &ui_hr();
# Show partitions table
my @links = ( "<a href='part_form.cgi?device=".&urlize($disk->{'device'}).
"&slice=$in{'slice'}'>".$text{'slice_add'}."</a>" );
if (@{$slice->{'parts'}}) {
print &ui_links_row(\@links);
print &ui_columns_start([
$text{'slice_letter'},
$text{'slice_type'},
$text{'slice_extent'},
$text{'slice_size'},
$text{'slice_start'},
$text{'slice_end'},
$text{'slice_use'},
]);
foreach my $p (@{$slice->{'parts'}}) {
# Create images for the extent
my $ext = "";
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($p->{'startblock'} - 1) /
$slice->{'blocks'};
$ext .= sprintf "<img src=images/%s.gif height=10 width=%d>",
$p->{'extended'} ? "ext" : "use",
$extwidth*($p->{'blocks'}) /
$slice->{'blocks'};
$ext .= sprintf "<img src=images/gap.gif height=10 width=%d>",
$extwidth*($slice->{'blocks'} - $p->{'startblock'} -
$p->{'blocks'}) / $slice->{'blocks'};
# Work out use
my @st = &fdisk::device_status($p->{'device'});
my $use = &fdisk::device_status_link(@st);
# Add row for the partition
my $url = "edit_part.cgi?device=".&urlize($disk->{'device'}).
"&slice=".$slice->{'number'}."&part=".$p->{'letter'};
print &ui_columns_row([
"<a href='$url'>".uc($p->{'letter'})."</a>",
"<a href='$url'>$p->{'type'}</a>",
$ext,
&nice_size($p->{'size'}),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$use,
]);
}
print &ui_columns_end();
print &ui_links_row(\@links);
}
print ui_table_start($text{'slice_header'}, undef, 2);
print ui_table_row($text{'part_device'}, "<tt>$slice->{'device'}</tt>");
my $slice_bytes = bytes_from_blocks($slice->{'device'}, $slice->{'blocks'});
print ui_table_row($text{'slice_ssize'}, $slice_bytes ? safe_nice_size($slice_bytes) : '-');
print ui_table_row($text{'slice_sstart'}, $slice->{'startblock'});
print ui_table_row($text{'slice_send'}, $slice->{'startblock'} + $slice->{'blocks'} - 1);
# Slice type selector (GPT vs legacy)
if (is_using_gpart()) {
my $scheme = ($disk_structure && $disk_structure->{'scheme'}) ? $disk_structure->{'scheme'} : 'GPT';
my @opts = list_partition_types($scheme);
# Default sensibly per scheme
my $default_type = ($scheme =~ /GPT/i) ? 'freebsd-zfs' : 'freebsd';
print ui_table_row($text{'slice_stype'}, ui_select("type", $slice->{'type'} || $default_type, \@opts));
}
else {
# No partitions yet
if (@st) {
# And directly in use, so none can be created
print "<b>$text{'slice_none2'}</b><p>\n";
}
else {
# Show link to add first partition
print "<b>$text{'slice_none'}</b><p>\n";
print &ui_links_row(\@links);
}
}
# Pre-cache tag options for the slice type select (legacy fdisk)
my @tags = fdisk::list_tags();
my @tag_options = map { [ $_, fdisk::tag_name($_) ] } @tags;
@tag_options = sort { $a->[1] cmp $b->[1] } @tag_options;
print ui_table_row($text{'slice_stype'}, ui_select("type", $slice->{'type'}, \@tag_options));
}
# Active slice - only applicable for legacy MBR. For GPT/UEFI and for EFI/freebsd-boot types, the active flag is irrelevant.
my $is_gpt = is_using_gpart() && ($disk_structure && $disk_structure->{'scheme'} && $disk_structure->{'scheme'} =~ /GPT/i);
if (!$is_gpt && ($slice->{'type'} !~ /^(?:efi|freebsd-boot)$/i)) {
my $active_default = $slice->{'active'} ? 1 : 0;
print ui_table_row($text{'slice_sactive'}, ui_yesno_radio("active", $active_default));
} else {
# Do not offer the control; display 'No' since active is not used here
print ui_table_row($text{'slice_sactive'}, $text{'no'});
}
print ui_table_row($text{'slice_suse'},
(!$slice_use || $slice_use eq $text{'part_nouse'})
? $text{'part_nouse'}
: ($slice_status[2] ? text('part_inuse', $slice_use) : text('part_foruse', $slice_use)));
# Add a row for the slice role
print ui_table_row($text{'slice_role'}, get_partition_role($slice));
print ui_table_end();
print ui_form_end([ [ undef, $text{'save'} ] ]);
print ui_hr();
# Show partitions table (only for MBR slices that support BSD disklabel)
my $can_have_parts = 0;
if (!is_using_gpart()) {
# Legacy MBR with BSD disklabel
$can_have_parts = 1;
} elsif ($disk_structure && $disk_structure->{'scheme'} && $disk_structure->{'scheme'} !~ /GPT/i) {
# MBR-style slice
$can_have_parts = 1;
}
my @links = $can_have_parts ? ( "<a href='part_form.cgi?device=" . urlize($disk->{'device'}) . "&slice=$in{'slice'}'>" . $text{'slice_add'} . "</a>" ) : ();
if (@{$slice->{'parts'}}) {
print ui_links_row(\@links) if @links;
print ui_columns_start([
$text{'slice_letter'},
$text{'slice_type'},
$text{'slice_extent'},
$text{'slice_size'},
$text{'slice_start'},
$text{'slice_end'},
$text{'disk_stripesize'},
$text{'slice_use'},
$text{'slice_role'},
]);
# Pre-calculate scaling factor for the partition extent images
my $scale = $extwidth / $slice->{'blocks'};
foreach my $p (@{$slice->{'parts'}}) {
# Create images representing the partition extent
my $gap_before = sprintf("<img src=images/gap.gif height=10 width=%d>", int($scale * ($p->{'startblock'} - 1)));
my $img_type = $p->{'extended'} ? "ext" : "use";
my $partition_img = sprintf("<img src=images/%s.gif height=10 width=%d>", $img_type, int($scale * $p->{'blocks'}));
my $gap_after = sprintf("<img src=images/gap.gif height=10 width=%d>", int($scale * ($slice->{'blocks'} - $p->{'startblock'} - $p->{'blocks'})));
my $ext = $gap_before . $partition_img . $gap_after;
# Cache partition device status information
my @part_status = fdisk::device_status($p->{'device'});
my $part_use = $zfs_info->{$p->{'device'}} || fdisk::device_status_link(@part_status);
# Prefer GEOM details for stripesize
my $ginfo = get_detailed_disk_info($p->{'device'});
my $stripesize = ($ginfo && $ginfo->{'stripesize'}) ? $ginfo->{'stripesize'} : '-';
# Classify format/use/role via library helper
(my $pn = $p->{'device'}) =~ s{^/dev/}{};
my ($fmt, $use_txt, $role_txt) = classify_partition_row(
base_device => $base_device,
scheme => ($disk_structure->{'scheme'} || ''),
part_name => $pn,
entry_part_type => $p->{'type'},
zfs_devices => $zfs_devices,
);
$use_txt ||= $part_use;
$role_txt ||= get_partition_role($p);
# Build edit URL
my $url = "edit_part.cgi?device=" . urlize($disk->{'device'}) . "&slice=" . $slice->{'number'} . "&part=" . $p->{'letter'};
my $psz_b = bytes_from_blocks($p->{'device'}, $p->{'blocks'});
print ui_columns_row([
"<a href='$url'>" . uc($p->{'letter'}) . "</a>",
"<a href='$url'>" . ($fmt || get_format_type($p)) . "</a>",
$ext,
($psz_b ? safe_nice_size($psz_b) : '-'),
$p->{'startblock'},
$p->{'startblock'} + $p->{'blocks'} - 1,
$stripesize,
$use_txt,
$role_txt,
]);
}
print ui_columns_end();
print ui_links_row(\@links) if @links;
} else {
# GPT partitions do not have sub-partitions
if (!$can_have_parts) {
# No message needed for GPT; partitions are top-level
}
# If slice is in use by a filesystem OR it is a boot slice, do not allow creating partitions
elsif (@slice_status || $zfs_info->{$slice->{'device'}} || $is_boot) {
print "<b>$text{'slice_none2'}</b><p>\n";
} else {
print "<b>$text{'slice_none'}</b><p>\n";
print ui_links_row(\@links) if @links;
}
}
if ($canedit && !$is_boot) { # Do not allow editing boot slices
print ui_hr();
print ui_buttons_start();
if (!@{$slice->{'parts'}}) {
show_filesystem_buttons($hiddens, \@slice_status, $slice);
}
print ui_buttons_row(
'delete_slice.cgi',
$text{'slice_delete'},
$text{'slice_deletedesc'},
ui_hidden("device", $in{'device'}) . "\n" . ui_hidden("slice", $in{'slice'})
);
print ui_buttons_end();
}
# SMART button (physical device)
if (&has_command("smartctl")) {
print ui_hr();
print ui_buttons_start();
print ui_buttons_row("smart.cgi", $text{'disk_smart'}, $text{'disk_smartdesc'},
ui_hidden("device", $disk->{'device'}));
print ui_buttons_end();
}
if ($canedit) {
print &ui_hr();
print &ui_buttons_start();
if (!@{$slice->{'parts'}}) {
&show_filesystem_buttons($hiddens, \@st, $slice);
}
# Button to delete slice
print &ui_buttons_row(
'delete_slice.cgi',
$text{'slice_delete'},
$text{'slice_deletedesc'},
&ui_hidden("device", $in{'device'})."\n".
&ui_hidden("slice", $in{'slice'}));
print &ui_buttons_end();
}
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
ui_print_footer("edit_disk.cgi?device=$in{'device'}", $text{'disk_return'});

View File

@@ -27,28 +27,54 @@ else {
$object = $slice;
}
# Safety checks: do not run fsck on boot partitions or in-use devices
if (is_boot_partition($object)) {
&error($in{'part'} ne '' ? $text{'part_eboot'} : $text{'slice_eboot'});
}
my @st_obj = &fdisk::device_status($object->{'device'});
my $use_obj = &fdisk::device_status_link(@st_obj);
if (@st_obj && $st_obj[2]) {
&error(&text('part_esave', $use_obj));
}
&ui_print_unbuffered_header($object->{'desc'}, $text{'fsck_title'}, "");
# Do the creation
print &text('fsck_checking', "<tt>$object->{'device'}</tt>"),"<br>\n";
print "<pre>\n";
my $cmd = &get_check_filesystem_command($disk, $slice, $part);
&additional_log('exec', undef, $cmd);
my $fh = "CMD";
&open_execute_command($fh, $cmd, 2);
while(<$fh>) {
print &html_escape($_);
}
close($fh);
print "</pre>";
if ($?) {
print $text{'fsck_failed'},"<p>\n";
}
else {
print $text{'fsck_done'},"<p>\n";
}
# If device is ZFS, do not run fsck; show zpool status instead
my $zmap = get_all_zfs_info();
if ($zmap->{$object->{'device'}}) {
my $pool = $zmap->{$object->{'device'}}; $pool =~ s/^.*?\b([A-Za-z0-9_\-]+)\b.*$/$1/;
print &text('fsck_checking', "<tt>$object->{'device'}</tt>"),"<br>\n";
print "<pre>\n";
my $cmd = "zpool status 2>&1";
&additional_log('exec', undef, $cmd);
print &html_escape(&backquote_command($cmd));
print "</pre>";
print $text{'fsck_done'},"<p>\n";
} else {
# Do the creation
print &text('fsck_checking', "<tt>$object->{'device'}</tt>"),"<br>\n";
print "<pre>\n";
my $cmd = &get_check_filesystem_command($disk, $slice, $part);
&additional_log('exec', undef, $cmd);
my $fh;
&open_execute_command($fh, $cmd, 2);
if ($fh) {
while (my $line = <$fh>) {
$line =~ s/[^\x09\x0A\x0D\x20-\x7E]//g;
print &html_escape($line);
}
close($fh);
}
print "</pre>";
if ($?) {
print $text{'fsck_failed'},"<p>\n";
}
else {
print $text{'fsck_done'},"<p>\n";
}
}
&webmin_log("fsck", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
$object->{'device'}, $object);
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
@@ -59,4 +85,4 @@ else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
}

BIN
bsdfdisk/images/free.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

View File

@@ -1,41 +1,57 @@
#!/usr/local/bin/perl
# Show a list of disks
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, %config, $module_name);
&ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0,
&help_search_link("fdisk", "man"));
my $err = &check_fdisk();
# Check prerequisites first
my $err = check_fdisk();
if ($err) {
&ui_print_endpage(&text('index_problem', $err));
}
ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0);
print "<b>$text{'index_problem'}</b><br>\n$err\n";
ui_print_footer("/", $text{'index_return'});
exit;
}
# Print header with help link
ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1, 0,
help_search_link("fdisk", "man"));
# List and sort disks by device name
my @disks = list_disks_partitions();
@disks = sort { ($a->{'device'}//'') cmp ($b->{'device'}//'') } @disks;
my @disks = &list_disks_partitions();
@disks = sort { $a->{'device'} cmp $b->{'device'} } @disks;
if (@disks) {
print &ui_columns_start([ $text{'index_dname'},
$text{'index_dsize'},
$text{'index_dmodel'},
$text{'index_dparts'} ]);
foreach my $d (@disks) {
print &ui_columns_row([
"<a href='edit_disk.cgi?device=".&urlize($d->{'device'}).
"'>".&partition_description($d->{'device'})."</a>",
&nice_size($d->{'size'}),
$d->{'model'},
scalar(@{$d->{'slices'}}),
]);
}
print &ui_columns_end();
}
else {
print "<b>$text{'index_none'}</b> <p>\n";
}
print ui_columns_start([
$text{'index_dname'},
$text{'index_dsize'},
$text{'index_dmodel'},
$text{'index_dparts'}
]);
&ui_print_footer("/", $text{'index'});
foreach my $d (@disks) {
my $device = $d->{'device'} // '';
my $disk_name = $device; $disk_name =~ s{^/dev/}{};
# Prefer mediasize from gpart list (bytes); fallback to diskinfo size
my $base = $device; $base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
my $bytes = $ds && $ds->{'mediasize'} ? $ds->{'mediasize'} : $d->{'size'};
my $size_display = defined $bytes ? safe_nice_size($bytes) : '-';
my $model = $d->{'model'} // '-';
my $url_device = urlize($device);
my $slices_cnt = scalar(@{ $d->{'slices'} || [] });
print ui_columns_row([
"<a href='edit_disk.cgi?device=$url_device'>$disk_name</a>",
$size_display,
$model, # Now correctly populated from bsdfdisk-lib.pl
$slices_cnt,
]);
}
print ui_columns_end();
}
else {
print "<b>$text{'index_none'}</b><p>\n";
}
ui_print_footer("/", $text{'index_return'});

View File

@@ -1,42 +1,92 @@
index_title=Partitions on Local Disks
index_ecmd=The required command $1 is missing
index_problem=This module cannot be used : $1
index_none=No disks were found on this system!
index_ecmd=Missing required command $1
index_problem=Cannot use this module : $1
index_none=No disks found on this system!
index_dname=Disk name
index_dsize=Total size
index_dmodel=Make and model
index_dparts=Slices
index_return=list of disks
index_format=Format
index_efdisk=You must have $1 or $2 installed to use this module
disk_edevice=Invalid device parameter
nslice_etype=Invalid slice type
# Disk overview
disk_title=Edit Disk
disk_egone=Disk no longer exists!
disk_no=Slice
disk_type=Type
disk_extent=Extent
disk_start=Start block
disk_end=End block
disk_use=Used by
disk_scount=$1 partitions
disk_parts=Partitions
disk_free=Free space
disk_vm=Virtual memory
disk_iscsi=iSCSI shared device $1
disk_none=This disk has no slices yet.
disk_size=Size
disk_dsize=<b>Disk size:</b> $1
disk_model=<b>Make and model:</b> $1
disk_cylinders=<b>Cylinders:</b> $1
disk_blocks=<b>Blocks:</b> $1
disk_device=<b>Device file:</b> $1
disk_dsize=Disk size
disk_model=Make and model
disk_cylinders=Cylinders
disk_blocks=Blocks
disk_device=Device file
disk_return=disk details and list of slices
disk_add=Create a new slice.
disk_smart=Show SMART Status
disk_smartdesc=Show the current status of this drive as detected by SMART, and check it for disk errors.
disk_no=No.
disk_partno=Part. No.
disk_partname=Part. Name
disk_partlabel=Part. Label
disk_subpart=Sub-part.
disk_size=Size
disk_free_space=Free Space
disk_available=Available
disk_format=Format
disk_use=Used by
disk_role=Role Type
select_device=$1 device $2
select_slice=$1 device $2 slice $3
select_part=$1 device $2 slice $3 partition $4
# Debug and scheme
disk_show_debug=Show raw debug information
disk_hide_debug=Hide debug information
disk_scheme=Partition Scheme
disk_unknown=Unknown
disk_sectorsize=Sector Size
disk_bytes=bytes
# GEOM information
disk_geom_header=GEOM Information
disk_geom_details=GEOM Disk Details
disk_mediasize=Media Size
disk_stripesize=Stripe Size
disk_stripeoffset=Stripe Offset
disk_mode=Mode
disk_rotationrate=Rotation Rate
disk_ident=Identifier
disk_lunid=LUN ID
disk_descr=Description
disk_rpm=RPM
disk_ssd=SSD (Solid State Drive)
# Debug panels
disk_debug_gpart=GPART Output
disk_debug_structure=Parsed Disk Structure
disk_debug_geom=GEOM Disk List
disk_debug_zfs=ZFS Status
disk_debug_zfs_cache=ZFS Devices Cache
# Roles and usage
disk_inzfs=In ZFS pool
disk_zfs_log=ZFS Log Device
disk_zfs_cache=ZFS Cache Device
disk_zfs_spare=ZFS Spare Device
disk_zfs_mirror=ZFS Mirror
disk_zfs_stripe=ZFS Stripe
disk_zfs_single=ZFS Single Device
disk_zfs_data=ZFS Data Device
disk_boot=Boot Partition
disk_boot_role=System Boot
disk_swap=Swap
disk_swap_role=Virtual Memory
# Slice pages
slice_title=Edit Slice
slice_egone=Selected slice does not exist!
slice_ssize=Slice size
@@ -56,55 +106,70 @@ slice_use=Used by
slice_none=This slice has no partitions yet.
slice_none2=This slice has no partitions, and none can be created as it is in use as a filesystem.
slice_delete=Delete Slice
slice_deletedesc=Delete this slice and all partitions and filesystems within it. Any data on those filesystem will be almost certainly unrecoverable.
slice_deletedesc=Delete this slice and all partitions and filesystems within it. Any data on that filesystem will be almost certainly unrecoverable.
slice_return=slice details and list of partitions
slice_err=Failed to modify slice
slice_header=Slice details
slice_suse=Directly used by
slice_adddesc=Create a new partition within this slice.
slice_cannotedit=This slice cannot be modified as it is currently in use.
slice_eboot=Cannot delete this slice as it contains boot partitions
slice_bootdesc=This slice contains bootloader code or kernel files needed to start the system
slice_role=Role
# Delete slice dialogs
dslice_title=Delete Slice
dslice_rusure=Are you sure you want to delete the slice $1? Any partitions and filesystems within it will also be deleted.
dslice_rusure=Are you sure you want to delete the slice $1 ? Any partitions and filesystems within it will also be deleted.
dslice_warn=Warning - this slice is currently used by : $1
dslice_confirm=Delete Now
dslice_deleting=Deleting slice $1 ..
dslice_failed=.. deletion failed : $1
dslice_done=.. done
# Create slice
nslice_title=Create Slice
nslice_header=New slice details
nslice_number=Slice number
nslice_autonext=Will auto-select next index
nslice_diskblocks=Disk size in blocks
nslice_start=Starting block
nslice_end=Ending block
nslice_type=New slice type
nslice_makepart=Create default partition?
nslice_err=Failed to create slice
nslice_enumber=Missing or non-numeric slice number
nslice_enumber=Slice number must be a number
nslice_eclash=A slice with number $1 already exists
nslice_estart=Starting block must be a number
nslice_eend=Ending block must be a number
nslice_erange=Starting block must be lower than the ending block
nslice_emax=Ending block cannot be larger than the disk size of $1 blocks
nslice_erange=Starting block must be before ending block
nslice_emax=Ending block cannot be larger than disk size of $1 blocks
nslice_creating=Creating slice $1 on $2 ..
nslice_failed=.. slice creation failed : $1
nslice_failed=.. creation failed : $1
nslice_done=.. slice added
nslice_parting=Create default partitions in slice $1 on $2 ..
nslice_pfailed=.. partition creation failed : $1
nslice_pdone=.. partition added
nslice_existing_header=Existing slices on this disk
nslice_existing_parts=Existing partitions on this disk
nslice_enospace=No space on device left to create a slice!
epart_existing=Existing partitions on this slice
# Create partition
npart_title=Create Partition
npart_header=New partition details
npart_letter=Partition letter
npart_diskblocks=Slice size in blocks
npart_slicerel=(slice-relative)
npart_creserved=partition 'c' is reserved
npart_type=Partition type
npart_err=Failed to create partition
npart_eletter=Partition number must be a letter from A to D
npart_eletter=Partition letter must be a-h (excluding c)
npart_ereserved=Partition 'c' is reserved for the whole slice in BSD disklabels
npart_eclash=A partition with letter $1 already exists
npart_emax=Ending block cannot be larger than the slice size of $1 blocks
npart_emax=Ending block cannot be larger than slice size of $1 blocks
npart_erange=Start block must be less than end block
npart_creating=Creating partition $1 on $2 ..
npart_failed=.. partition creation failed : $1
npart_done=.. partition added
# Edit partition
part_title=Edit Partition
part_egone=Partition no longer exists!
part_header=Partition details
@@ -128,18 +193,29 @@ part_err=Failed to save partition
part_esave=Currently in use by $1
part_newmount=Mount Partition On:
part_newmount2=Mount Partition
part_mountmsg=Mount this device on new directory on your system, so that it can be used to store files. A filesystem must have been already created on the device.
part_mountmsg2=Mount this device as virtual memory on your system, to increase the amount of memory available.
part_mountmsg=Mount this device on a new directory on your system, so that it can be used to store files. A filesystem must already be created on the device.
part_mountmsg2=Mount this device as virtual memory on your system to increase the amount of available memory.
part_cannotedit=This partition cannot be modified as it is currently in use.
part_boot=Boot Partition
part_eboot=Cannot delete boot partitions as this may render the system unbootable
part_bootdesc=This partition contains bootloader code or kernel files needed to start the system
part_zfslog=ZFS Log Device
part_zfsdata=ZFS Data Device
part_mounted=Mounted on $1
part_unused=Not in use
part_bootcannotedit=This partition cannot be modified as it is a boot partition. Changing it could render the system unbootable.
part_role=Role
# Delete partition dialogs
dpart_title=Delete Partition
dpart_rusure=Are you sure you want to delete the partition $1? Any filesystems within it will also be deleted.
dpart_rusure=Are you sure you want to delete the partition $1 ? Any filesystems in it will also be deleted.
dpart_warn=Warning - this partition is currently used by $1
dpart_confirm=Delete Now
dpart_deleting=Deleting partition $1 ..
dpart_failed=.. deletion failed : $1
dpart_done=.. done
# New filesystem
newfs_title=Create Filesystem
newfs_header=New filesystem details
newfs_free=Space to reserve for root
@@ -147,27 +223,36 @@ newfs_deffree=Default (8%)
newfs_trim=Enable TRIM mode for SSDs
newfs_label=Filesystem label
newfs_none=None
newfs_create=Create Now
newfs_err=Failed to create filesystem
newfs_efree=Space to reserve for root must be a percentage
newfs_elabel=Missing or invalid label
newfs_efree=Missing or invalid percentage of free space
newfs_elabel=Missing or invalid filesystem label
newfs_creating=Creating filesystem on $1 ..
newfs_failed=.. creation failed!
newfs_done=.. created successfully
newfs_failed=.. creation failed : $1
newfs_done=.. filesystem created
# FS check
fsck_title=Check Filesystem
fsck_header=Filesystem check options
fsck_repair=Repair mode
fsck_fix=Only fix safe errors
fsck_fix2=Try to fix all errors
fsck_err=Failed to check filesystem
fsck_checking=Checking filesystem on $1 ..
fsck_exec=Executing command $1 ..
fsck_failed=.. check failed!
fsck_done=.. check completed with no errors found
fsck_done=.. check complete
fsck_checking=Checking filesystem on $1 ..
log_create_slice=Created slice $1
log_delete_slice=Deleted slice $1
log_modify_slice=Modified slice $1
log_create_part=Created partition $1
log_delete_part=Deleted partition $1
log_modify_part=Modified partition $1
log_newfs_part=Created filesystem on partition $1
log_fsck_part=Checked filesystem on partition $1
# Logging
action_create_slice=Created slice $1
action_delete_slice=Deleted slice $1
action_modify_slice=Modified slice $1
action_create_part=Created partition $1
action_delete_part=Deleted partition $1
action_modify_part=Modified partition $1
action_create_fs=Created filesystem on $1
action_check_fs=Checked filesystem on $1
__norefs=1
# Generic
save=Save
yes=Yes
no=No

View File

@@ -27,13 +27,25 @@ else {
$object = $slice;
}
# Safety checks: do not run newfs on boot partitions or in-use devices
if (is_boot_partition($object)) {
&error($in{'part'} ne '' ? $text{'part_eboot'} : $text{'slice_eboot'});
}
my @st_obj = &fdisk::device_status($object->{'device'});
my $use_obj = &fdisk::device_status_link(@st_obj);
if (@st_obj && $st_obj[2]) {
&error(&text('part_esave', $use_obj));
}
# Validate inputs
my $newfs = { };
$in{'free_def'} || $in{'free'} =~ /^\d+$/ && $in{'free'} <= 100 ||
$in{'free_def'} ||
($in{'free'} =~ /^\d+$/ && $in{'free'} >= 0 && $in{'free'} <= 100) ||
&error($text{'newfs_efree'});
$newfs->{'free'} = $in{'free_def'} ? undef : $in{'free'};
$newfs->{'trim'} = $in{'trim'};
$in{'label_def'} || $in{'label'} =~ /^\S+$/ ||
$in{'label_def'} ||
length($in{'label'}) > 0 ||
&error($text{'newfs_elabel'});
$newfs->{'label'} = $in{'label_def'} ? undef : $in{'label'};
@@ -44,12 +56,14 @@ print &text('newfs_creating', "<tt>$object->{'device'}</tt>"),"<br>\n";
print "<pre>\n";
my $cmd = &get_create_filesystem_command($disk, $slice, $part, $newfs);
&additional_log('exec', undef, $cmd);
my $fh = "CMD";
my $fh;
&open_execute_command($fh, $cmd, 2);
while(<$fh>) {
print &html_escape($_);
}
close($fh);
if ($fh) {
while(<$fh>) {
print &html_escape($_);
}
close($fh);
}
print "</pre>";
if ($?) {
print $text{'newfs_failed'},"<p>\n";
@@ -58,6 +72,18 @@ else {
print $text{'newfs_done'},"<p>\n";
&webmin_log("newfs", $in{'part'} ne '' ? "part" : "object",
$object->{'device'}, $object);
# If a label was provided, set the partition label (GPT slice or BSD sub-partition)
if (!$in{'label_def'} && defined $in{'label'} && length $in{'label'}) {
my $errlbl = set_partition_label(
disk => $disk,
slice => $slice,
part => ($in{'part'} ne '' ? $part : undef),
label => $in{'label'}
);
if ($errlbl) {
print "Warning: failed to set partition label: \n" . &html_escape($errlbl) . "\n";
}
}
}
if ($in{'part'} ne '') {
@@ -69,4 +95,4 @@ else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
}

View File

@@ -10,21 +10,27 @@ our (%in, %text, $module_name);
&ReadParse();
# Get the disk and slice
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my $object;
if ($in{'part'} ne '') {
my ($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
else {
$object = $slice;
}
# Validate input parameters to prevent command injection
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name");
$in{'device'} !~ /\.\./ or &error("Invalid device name");
$in{'slice'} =~ /^\d+$/ or &error("Invalid slice number") if $in{'slice'};
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'};
my @disks = &list_disks_partitions();
my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks;
$disk || &error($text{'disk_egone'});
my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{$disk->{'slices'}};
$slice || &error($text{'slice_egone'});
my $object;
if ($in{'part'} ne '') {
$in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter");
my ($part) = grep { $_->{'letter'} eq $in{'part'} }
@{$slice->{'parts'}};
$part || &error($text{'part_egone'});
$object = $part;
}
else {
$object = $slice;
}
&ui_print_header($object->{'desc'}, $text{'newfs_title'}, "");
@@ -38,24 +44,24 @@ print &ui_table_row($text{'part_device'},
"<tt>$object->{'device'}</tt>");
print &ui_table_row($text{'newfs_free'},
&ui_opt_textbox("free", undef, 4, $text{'newfs_deffree'})."%");
&ui_opt_textbox("free", undef, 4, $text{'newfs_deffree'}) . "%");
print &ui_table_row($text{'newfs_trim'},
&ui_yesno_radio("trim", 0));
&ui_yesno_radio("trim", 0));
print &ui_table_row($text{'newfs_label'},
&ui_opt_textbox("label", undef, 20, $text{'newfs_none'}));
&ui_opt_textbox("label", undef, 20, $text{'newfs_none'}));
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'newfs_create'} ] ]);
print &ui_form_end([ [ undef, $text{'save'} ] ]);
if ($in{'part'} ne '') {
&ui_print_footer("edit_part.cgi?device=$in{'device'}&".
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
&ui_print_footer("edit_part.cgi?device=$in{'device'}&" .
"slice=$in{'slice'}&part=$in{'part'}",
$text{'part_return'});
}
else {
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&".
"slice=$in{'slice'}",
$text{'slice_return'});
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&" .
"slice=$in{'slice'}",
$text{'slice_return'});
}

View File

@@ -23,37 +23,65 @@ print &ui_hidden("device", $in{'device'});
print &ui_hidden("slice", $in{'slice'});
print &ui_table_start($text{'npart_header'}, undef, 2);
# Partition number (first free)
# Partition number (first free, skipping 'c' which is reserved for the whole slice)
my %used = map { $_->{'letter'}, $_ } @{$slice->{'parts'}};
$used{'c'} = 1; # Reserve 'c' for the whole slice (BSD convention)
my $l = 'a';
while($used{$l}) {
$l++;
}
print &ui_table_row($text{'npart_letter'},
&ui_textbox("letter", $l, 4));
&ui_textbox("letter", $l, 4) . " <i>(" . $text{'npart_creserved'} . ")</i>");
# Slice size in blocks
print &ui_table_row($text{'npart_diskblocks'},
$slice->{'blocks'});
# Start and end blocks (defaults to last part)
my ($start, $end) = (0, $slice->{'blocks'});
foreach my $p (sort { $a->{'startblock'} cmp $b->{'startblock'} }
@{$slice->{'parts'}}) {
$start = $p->{'startblock'} + $p->{'blocks'} + 1;
}
print &ui_table_row($text{'nslice_start'},
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'},
&ui_textbox("end", $end, 10));
# Start and end blocks for BSD partitions are SLICE-RELATIVE (not disk-absolute)
# Start at 0 (or after last partition), end at slice size - 1
my ($start, $end) = (0, $slice->{'blocks'} - 1);
foreach my $p (sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{$slice->{'parts'}}) {
# Partitions are already stored as slice-relative
$start = $p->{'startblock'} + $p->{'blocks'};
}
if (defined $in{'start'} && $in{'start'} =~ /^\d+$/) { $start = $in{'start'}; }
if (defined $in{'end'} && $in{'end'} =~ /^\d+$/) { $end = $in{'end'}; }
print &ui_table_row($text{'nslice_start'} . " " . $text{'npart_slicerel'},
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'} . " " . $text{'npart_slicerel'},
&ui_textbox("end", $end, 10));
# Partition type
# For BSD-on-MBR inner label partitions, offer FreeBSD partition types
my $scheme = 'BSD';
my $default_ptype = 'freebsd-ufs';
print &ui_table_row($text{'npart_type'},
&ui_select("type", '4.2BSD',
[ &list_partition_types() ]));
&ui_select("type", $default_ptype,
[ list_partition_types($scheme) ]));
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'create'} ] ]);
print &ui_form_end([ [ undef, $text{'save'} ] ]);
# Existing partitions summary
if (@{$slice->{'parts'}||[]}) {
my $zfs = get_all_zfs_info();
print &ui_hr();
print &ui_columns_start([
$text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'}, $text{'slice_end'}, $text{'slice_size'}, $text{'slice_use'}, $text{'slice_role'}
], $text{'epart_existing'});
foreach my $p (sort { $a->{'startblock'} <=> $b->{'startblock'} } @{$slice->{'parts'}}) {
my $ptype = get_type_description($p->{'type'}) || $p->{'type'};
my @stp = fdisk::device_status($p->{'device'});
my $usep = $zfs->{$p->{'device'}} || fdisk::device_status_link(@stp) || $text{'part_nouse'};
my $rolep = get_partition_role($p);
my $pb = bytes_from_blocks($p->{'device'}, $p->{'blocks'});
print &ui_columns_row([
uc($p->{'letter'}), $ptype, $p->{'startblock'}, $p->{'startblock'} + $p->{'blocks'} - 1, ($pb ? safe_nice_size($pb) : '-'), $usep, $rolep
]);
}
print &ui_columns_end();
}
&ui_print_footer("edit_slice.cgi?device=$in{'device'}&slice=$in{'slice'}",
$text{'slice_return'});
$text{'slice_return'});

View File

@@ -20,9 +20,22 @@ $slice || &error($text{'slice_egone'});
# Apply changes
my $oldslice = { %$slice };
$slice->{'type'} = $in{'type'};
if (!$slice->{'active'}) {
$slice->{'active'} = $in{'active'};
}
$slice->{'active'} = $in{'active'} if (defined $in{'active'});
# Apply active flag for MBR disks via gpart set/unset when it changed
my $base = $disk->{'device'}; $base =~ s{^/dev/}{};
my $ds = get_disk_structure($base);
if (is_using_gpart() && $ds && $ds->{'scheme'} && $ds->{'scheme'} !~ /GPT/i) {
my $idx = slice_number($slice);
if (defined $oldslice->{'active'} && defined $slice->{'active'} && $oldslice->{'active'} != $slice->{'active'}) {
my $cmd = $slice->{'active'} ? "gpart set -a active -i $idx $base" : "gpart unset -a active -i $idx $base";
my $out = `$cmd 2>&1`;
if ($? != 0) {
&error("Failed to change active flag: $out");
}
}
}
my $err = &modify_slice($disk, $oldslice, $slice);
&error($err) if ($err);

View File

@@ -16,6 +16,29 @@ $disk || &error($text{'disk_egone'});
&ui_print_header($disk->{'desc'}, $text{'nslice_title'}, "");
# Determine scheme for read-only behavior and note
my $base_device = $disk->{'device'}; $base_device =~ s{^/dev/}{};
my $disk_structure = get_disk_structure($base_device);
my $is_gpt = (is_using_gpart() && $disk_structure && ($disk_structure->{'scheme'}||'') =~ /GPT/i);
# Check if there is any free space on the device
my $has_free_space = 0;
if ($disk_structure && $disk_structure->{'entries'}) {
foreach my $entry (@{$disk_structure->{'entries'}}) {
if ($entry->{'type'} eq 'free' && $entry->{'size'} > 0) {
$has_free_space = 1;
last;
}
}
}
# If no free space, show error and return
if (!$has_free_space) {
print "<p><b>$text{'nslice_enospace'}</b></p>\n";
&ui_print_footer("edit_disk.cgi?device=$in{'device'}", $text{'disk_return'});
exit;
}
print &ui_form_start("create_slice.cgi", "post");
print &ui_hidden("device", $in{'device'});
print &ui_table_start($text{'nslice_header'}, undef, 2);
@@ -26,37 +49,86 @@ my $n = 1;
while($used{$n}) {
$n++;
}
print &ui_table_row($text{'nslice_number'},
&ui_textbox("number", $n, 6));
# Disk size in blocks
print &ui_table_row($text{'nslice_diskblocks'},
$disk->{'blocks'});
# Start and end blocks (defaults to last slice+1)
my ($start, $end) = (63, $disk->{'blocks'});
foreach my $s (sort { $a->{'startblock'} cmp $b->{'startblock'} }
my $num_field = $is_gpt
? "<input type='text' name='number' value='".&html_escape($n)."' size='6' readonly> <span style='color:#666;font-style:italic'>".$text{'nslice_autonext'}."</span>"
: &ui_textbox("number", $n, 6);
print &ui_table_row($text{'nslice_number'}, $num_field);
# Disk size in blocks (prefer GPART total blocks)
my $disk_blocks = ($disk_structure && $disk_structure->{'total_blocks'}) ? $disk_structure->{'total_blocks'} : ($disk->{'blocks'} || 0);
print &ui_table_row($text{'nslice_diskblocks'}, $disk_blocks);
# Start and end blocks (defaults to last slice+1). Allow prefill from query.
my ($start, $end) = (2048, $disk_blocks > 0 ? $disk_blocks - 1 : 0);
foreach my $s (sort { $a->{'startblock'} <=> $b->{'startblock'} }
@{$disk->{'slices'}}) {
$start = $s->{'startblock'} + $s->{'blocks'} + 1;
}
$start = $s->{'startblock'} + $s->{'blocks'}; # leave 1 block (512B) gap
}
if (defined $in{'start'} && $in{'start'} =~ /^\d+$/) { $start = $in{'start'}; }
if (defined $in{'end'} && $in{'end'} =~ /^\d+$/) { $end = $in{'end'}; }
print &ui_table_row($text{'nslice_start'},
&ui_textbox("start", $start, 10));
&ui_textbox("start", $start, 10));
print &ui_table_row($text{'nslice_end'},
&ui_textbox("end", $end, 10));
&ui_textbox("end", $end, 10));
# Slice type
print &ui_table_row($text{'nslice_type'},
&ui_select("type", 'a5',
[ sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] }
&fdisk::list_tags() ]));
# Also create partition?
print &ui_table_row($text{'nslice_makepart'},
&ui_yesno_radio("makepart", 1));
if (is_using_gpart()) {
my $scheme = ($disk_structure && $disk_structure->{'scheme'}) ? $disk_structure->{'scheme'} : 'GPT';
my $default_stype = ($scheme =~ /GPT/i) ? 'freebsd-zfs' : 'freebsd';
print &ui_table_row($text{'nslice_type'},
&ui_select("type", $default_stype,
[ list_partition_types($scheme) ]));
}
else {
print &ui_table_row($text{'nslice_type'},
&ui_select("type", 'a5',
[ sort { $a->[1] cmp $b->[1] }
map { [ $_, &fdisk::tag_name($_) ] }
&fdisk::list_tags() ]));
}
# Also create partition? (only for MBR slices with BSD disklabel support)
if (!$is_gpt) {
print &ui_table_row($text{'slice_add'},
&ui_yesno_radio("makepart", 1));
}
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'create'} ] ]);
print &ui_form_end([ [ undef, $text{'save'} ] ]);
# Existing slices summary
print &ui_hr();
print &ui_columns_start([$text{'disk_no'}, $text{'disk_type'}, $text{'disk_start'}, $text{'disk_end'}, $text{'disk_size'}], $text{'nslice_existing_header'});
foreach my $s (sort { $a->{'number'} <=> $b->{'number'} } @{$disk->{'slices'}}) {
my $stype = get_type_description($s->{'type'}) || $s->{'type'};
my $szb = bytes_from_blocks($s->{'device'}, $s->{'blocks'});
my $sz = defined $szb ? safe_nice_size($szb) : '-';
print &ui_columns_row([
$s->{'number'},
$stype,
$s->{'startblock'},
$s->{'startblock'} + $s->{'blocks'} - 1,
$sz,
]);
}
print &ui_columns_end();
# Existing partitions summary
my @parts_rows;
foreach my $s (sort { $a->{'number'} <=> $b->{'number'} } @{$disk->{'slices'}}) {
next unless @{$s->{'parts'}||[]};
foreach my $p (@{$s->{'parts'}}) {
my $ptype = get_type_description($p->{'type'}) || $p->{'type'};
my $pb = bytes_from_blocks($p->{'device'}, $p->{'blocks'});
my $psz = defined $pb ? safe_nice_size($pb) : '-';
push @parts_rows, [ $s->{'number'}, uc($p->{'letter'}), $ptype, $p->{'startblock'}, $p->{'startblock'} + $p->{'blocks'} - 1, $psz ];
}
}
if (@parts_rows) {
print &ui_columns_start(['Slice', $text{'slice_letter'}, $text{'slice_type'}, $text{'slice_start'}, $text{'slice_end'}, $text{'slice_size'}], $text{'nslice_existing_parts'});
foreach my $row (@parts_rows) { print &ui_columns_row($row); }
print &ui_columns_end();
}
&ui_print_footer("edit_disk.cgi?device=$in{'device'}",
$text{'disk_return'});
$text{'disk_return'});

31
bsdfdisk/smart.cgi Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/local/bin/perl
# Show SMART status for a given device
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
require './bsdfdisk-lib.pl';
our (%in, %text, $module_name);
&ReadParse();
# Validate device param
$in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error($text{'disk_edevice'} || 'Invalid device');
$in{'device'} !~ /\.\./ or &error($text{'disk_edevice'} || 'Invalid device');
# Check smartctl availability
&has_command('smartctl') or &error($text{'index_ecmd'} ? &text('index_ecmd','smartctl') : 'smartctl not available');
my $device = $in{'device'};
my $dev_html = &html_escape($device);
&ui_print_header($dev_html, $text{'disk_smart'} || 'SMART Status', "");
print "<div class='panel panel-default'>\n";
print "<div class='panel-heading'><h3 class='panel-title'>SMART status for <tt>$dev_html</tt></h3></div>\n";
print "<div class='panel-body'>\n";
my $cmd = "smartctl -a " . &quote_path($device) . " 2>&1";
my $out = &backquote_command($cmd);
print "<pre>" . &html_escape("Command: $cmd\n\n$out") . "</pre>\n";
print "</div></div>\n";
&ui_print_footer("edit_disk.cgi?device=".&urlize($device), $text{'disk_return'});