#!/usr/local/bin/perl # Show details of a disk, and slices on it use strict; use warnings; no warnings 'redefine'; no warnings 'uninitialized'; require './bsdfdisk-lib.pl'; our (%in, %text, $module_name); &ReadParse(); 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'}, ""); # Debug toggle bar print "
"; if ($in{'debug'}) { print " $text{'disk_hide_debug'}"; } else { print " $text{'disk_show_debug'}"; } print "
"; # 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'}) { print &ui_table_row($text{'disk_model'}, $disk->{'model'}); } print &ui_table_row($text{'disk_device'}, "$disk->{'device'}"); # 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'}, "$text{'disk_geom_details'}", 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'}); } } 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'}, "$text{'disk_advanced_details'}", 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 "
"; # Debug: gpart show output my $cmd = "gpart show -l $base_device 2>&1"; my $out = &backquote_command($cmd); print "
"; print "

$text{'disk_debug_gpart'}

"; print "
"; print "
Command: $cmd\nOutput:\n$out\n
"; print "
"; # Debug: disk structure print "
"; print "

$text{'disk_debug_structure'}

"; print "
"; print "
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 "
"; print "
"; # Debug: Raw GEOM output print "
"; print "

$text{'disk_debug_geom'}

"; print "
"; print "
Raw GEOM output:\n";
    print &html_escape(&backquote_command("geom disk list " . "e_path($device) . " 2>/dev/null"));
    print "
"; print "
"; print "
"; } # 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 "
"; print "
"; print "

$text{'disk_debug_zfs'}

"; print "
"; print "
";
    my $cmd = "zpool status 2>&1";
    my $out = &backquote_command($cmd);
    print "Command: $cmd\nOutput:\n$out\n";
    print "
"; print "
"; print "
"; } # Debug: Print partition details mapping if debug enabled if ($in{'debug'}) { print "
"; print "
"; print "

$text{'disk_debug_part_details'}

"; print "
"; print "
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 "
"; print "
"; print "
"; } # Get sector size my $sectorsize = $disk_structure->{'sectorsize'} || &get_disk_sectorsize($device) || 512; my $sectorsize_text = $sectorsize ? "$sectorsize" : "512"; # Show partitions table my @links = ( "".$text{'disk_add'}."" ); 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, "".$text{'disk_free'}.""); push(@cols, "-"); push(@cols, "-"); push(@cols, "-"); my $ext = ""; $ext .= sprintf "", $scale * ($entry->{'start'} - 1); $ext .= sprintf "", $scale * ($entry->{'size'}); $ext .= sprintf "", $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 "", $scale * ($entry->{'start'} - 1); $ext .= sprintf "", $scale * ($entry->{'size'}); $ext .= sprintf "", $scale * ($disk_blocks - $entry->{'start'} - $entry->{'size'}); my $url = "edit_slice.cgi?device=$device_url&slice=".&urlize($part_num); push(@cols, "".&html_escape($part_num).""); 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 "", $scale * ($s->{'startblock'} - 1); $ext .= sprintf "", ($s->{'extended'} ? "ext" : "use"), $scale * ($s->{'blocks'}); $ext .= sprintf "", $scale * ($disk_blocks - $s->{'startblock'} - $s->{'blocks'}); my $url = "edit_slice.cgi?device=$device_url&slice=".&urlize($s->{'number'}); push(@cols, "".&html_escape($s->{'number'}).""); 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 "

$text{'disk_none'}

\n"; } } print &ui_links_row(\@links); # 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 "
"; print "
"; print "

$text{'disk_debug_zfs_cache'}

"; print "
"; print "
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 "
"; print "
"; print "
"; } &ui_print_footer("", $text{'disk_return'});