#!/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(); # Validate input parameters $in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error( $text{'disk_edevice'} ); $in{'device'} !~ /\.\./ or &error( $text{'disk_edevice'} ); 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'}, &html_escape( $disk->{'model'} ) ); } print &ui_table_row( $text{'disk_device'}, "" . &html_escape( $disk->{'device'} ) . "" ); # Get disk scheme print &ui_table_row( $text{'disk_scheme'}, $disk_structure ? &html_escape( $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'}, &html_escape( $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'}, &html_escape( $geom_info->{'ident'} ) ); } if ( $geom_info->{'lunid'} ) { print &ui_table_row( $text{'disk_lunid'}, &html_escape( $geom_info->{'lunid'} ) ); } if ( $geom_info->{'descr'} ) { print &ui_table_row( $text{'disk_descr'}, &html_escape( $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 " . "e_path($base_device) . " 2>&1"; my $out = &backquote_command($cmd); print "
"; print "

$text{'disk_debug_gpart'}

"; print "
"; print "
Command: "
      . &html_escape($cmd)
      . "\nOutput:\n"
      . &html_escape($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: " . &html_escape( $entry->{$k} ) . "\n";
                }
                print "  },\n";
            }
            print "]\n";
        }
        else {
            print "$key: " . &html_escape( $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: "
      . &html_escape($cmd)
      . "\nOutput:\n"
      . &html_escape($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: "
              . &html_escape( $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, &html_escape($part_name) ); my $part_label = $part_info ? $part_info->{'label'} : ( $entry->{'label'} eq "(null)" ? "-" : $entry->{'label'} ); push( @cols, &html_escape($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, &html_escape($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, &html_escape( $format_type || '-' ) ); push( @cols, &html_escape( $usage || $text{'part_nouse'} ) ); push( @cols, &html_escape( $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, &html_escape( $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'} );