diff --git a/bsdfdisk/bsdfdisk-lib.pl b/bsdfdisk/bsdfdisk-lib.pl
index 767fc9afc..977b00f43 100644
--- a/bsdfdisk/bsdfdisk-lib.pl
+++ b/bsdfdisk/bsdfdisk-lib.pl
@@ -1,178 +1,178 @@
-BEGIN { push( @INC, ".." ); }
-use WebminCore;
-init_config();
-foreign_require( "mount", "mount-lib.pl" );
-foreign_require( "fdisk", "fdisk-lib.pl" );
-
-#---------------------------------------------------------------------
-# Helper: Cache mount info
-# Returns a hash reference of device => mount point and an arrayref
-# containing mount entries.
-sub get_all_mount_points_cached {
- my %mount_info;
- my @mount_list = mount::list_mounted();
- foreach my $m (@mount_list) {
- $mount_info{ $m->[0] } = $m->[1];
- }
- my $swapinfo = backquote_command("swapinfo -k 2>/dev/null");
- foreach my $line ( split( /\n/, $swapinfo ) ) {
- if ( $line =~ /^(\/dev\/\S+)\s+\d+\s+\d+\s+\d+/ ) {
- $mount_info{$1} = "swap";
- }
- }
-
- # ZFS, GEOM, glabel, geli remain the same as the original.
- if ( has_command("zpool") ) {
- my $zpool_out = backquote_command("zpool status 2>/dev/null");
- my $current_pool = "";
- foreach my $line ( split( /\n/, $zpool_out ) ) {
- if ( $line =~ /^\s*pool:\s+(\S+)/ ) {
- $current_pool = $1;
- }
- elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
- my $dev = $1;
- $mount_info{$dev} = "ZFS pool: $current_pool";
- }
- }
- }
- if ( has_command("geom") ) {
-
- # gmirror
- my $gmirror_out = backquote_command("gmirror status 2>/dev/null");
- my $current_mirror = "";
- foreach my $line ( split( /\n/, $gmirror_out ) ) {
- if ( $line =~ /^(\S+):/ ) {
- $current_mirror = $1;
- }
- elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
- my $dev = $1;
- $mount_info{$dev} = "gmirror: $current_mirror";
- }
- }
-
- # gstripe
- my $gstripe_out = backquote_command("gstripe status 2>/dev/null");
- my $current_stripe = "";
- foreach my $line ( split( /\n/, $gstripe_out ) ) {
- if ( $line =~ /^(\S+):/ ) {
- $current_stripe = $1;
- }
- elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
- my $dev = $1;
- $mount_info{$dev} = "gstripe: $current_stripe";
- }
- }
-
- # graid
- my $graid_out = backquote_command("graid status 2>/dev/null");
- my $current_raid = "";
- foreach my $line ( split( /\n/, $graid_out ) ) {
- if ( $line =~ /^(\S+):/ ) {
- $current_raid = $1;
- }
- elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
- my $dev = $1;
- $mount_info{$dev} = "graid: $current_raid";
- }
- }
- }
- if ( has_command("glabel") ) {
- my $glabel_out = backquote_command("glabel status 2>/dev/null");
- foreach my $line ( split( /\n/, $glabel_out ) ) {
- if ( $line =~ /^\s*(\S+)\s+(\S+)\s+(\S+)/ ) {
- my $label = $1;
- my $dev = $3;
- if ( $dev =~ /^\/dev\// ) {
- $mount_info{$dev} = "glabel: $label";
- }
- }
- }
- }
- if ( has_command("geli") ) {
- my $geli_out = backquote_command("geli status 2>/dev/null");
- foreach my $line ( split( /\n/, $geli_out ) ) {
- if ( $line =~ /^(\/dev\/\S+)\s+/ ) {
- my $dev = $1;
- $mount_info{$dev} = "geli encrypted";
- }
- }
- }
- return ( \%mount_info, \@mount_list );
-}
-
-#---------------------------------------------------------------------
-# Helper: Get file statistics for a device (cached per device)
-sub get_dev_stat {
- my ($dev) = @_;
- if ( -e $dev ) {
- my @st = stat($dev);
- if (@st) {
- my $size = $st[7];
- my $blocks = int( $size / 512 );
- return ( $size, $blocks );
- }
- }
- return ( undef, undef );
-}
-
-#---------------------------------------------------------------------
-# is_boot_partition()
+BEGIN { push( @INC, ".." ); }
+use WebminCore;
+init_config();
+foreign_require( "mount", "mount-lib.pl" );
+foreign_require( "fdisk", "fdisk-lib.pl" );
+
+#---------------------------------------------------------------------
+# Helper: Cache mount info
+# Returns a hash reference of device => mount point and an arrayref
+# containing mount entries.
+sub get_all_mount_points_cached {
+ my %mount_info;
+ my @mount_list = mount::list_mounted();
+ foreach my $m (@mount_list) {
+ $mount_info{ $m->[0] } = $m->[1];
+ }
+ my $swapinfo = backquote_command("swapinfo -k 2>/dev/null");
+ foreach my $line ( split( /\n/, $swapinfo ) ) {
+ if ( $line =~ /^(\/dev\/\S+)\s+\d+\s+\d+\s+\d+/ ) {
+ $mount_info{$1} = "swap";
+ }
+ }
+
+ # ZFS, GEOM, glabel, geli remain the same as the original.
+ if ( has_command("zpool") ) {
+ my $zpool_out = backquote_command("zpool status 2>/dev/null");
+ my $current_pool = "";
+ foreach my $line ( split( /\n/, $zpool_out ) ) {
+ if ( $line =~ /^\s*pool:\s+(\S+)/ ) {
+ $current_pool = $1;
+ }
+ elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
+ my $dev = $1;
+ $mount_info{$dev} = "ZFS pool: $current_pool";
+ }
+ }
+ }
+ if ( has_command("geom") ) {
+
+ # gmirror
+ my $gmirror_out = backquote_command("gmirror status 2>/dev/null");
+ my $current_mirror = "";
+ foreach my $line ( split( /\n/, $gmirror_out ) ) {
+ if ( $line =~ /^(\S+):/ ) {
+ $current_mirror = $1;
+ }
+ elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
+ my $dev = $1;
+ $mount_info{$dev} = "gmirror: $current_mirror";
+ }
+ }
+
+ # gstripe
+ my $gstripe_out = backquote_command("gstripe status 2>/dev/null");
+ my $current_stripe = "";
+ foreach my $line ( split( /\n/, $gstripe_out ) ) {
+ if ( $line =~ /^(\S+):/ ) {
+ $current_stripe = $1;
+ }
+ elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
+ my $dev = $1;
+ $mount_info{$dev} = "gstripe: $current_stripe";
+ }
+ }
+
+ # graid
+ my $graid_out = backquote_command("graid status 2>/dev/null");
+ my $current_raid = "";
+ foreach my $line ( split( /\n/, $graid_out ) ) {
+ if ( $line =~ /^(\S+):/ ) {
+ $current_raid = $1;
+ }
+ elsif ( $line =~ /^\s*(\/dev\/\S+)/ ) {
+ my $dev = $1;
+ $mount_info{$dev} = "graid: $current_raid";
+ }
+ }
+ }
+ if ( has_command("glabel") ) {
+ my $glabel_out = backquote_command("glabel status 2>/dev/null");
+ foreach my $line ( split( /\n/, $glabel_out ) ) {
+ if ( $line =~ /^\s*(\S+)\s+(\S+)\s+(\S+)/ ) {
+ my $label = $1;
+ my $dev = $3;
+ if ( $dev =~ /^\/dev\// ) {
+ $mount_info{$dev} = "glabel: $label";
+ }
+ }
+ }
+ }
+ if ( has_command("geli") ) {
+ my $geli_out = backquote_command("geli status 2>/dev/null");
+ foreach my $line ( split( /\n/, $geli_out ) ) {
+ if ( $line =~ /^(\/dev\/\S+)\s+/ ) {
+ my $dev = $1;
+ $mount_info{$dev} = "geli encrypted";
+ }
+ }
+ }
+ return ( \%mount_info, \@mount_list );
+}
+
+#---------------------------------------------------------------------
+# Helper: Get file statistics for a device (cached per device)
+sub get_dev_stat {
+ my ($dev) = @_;
+ if ( -e $dev ) {
+ my @st = stat($dev);
+ if (@st) {
+ my $size = $st[7];
+ my $blocks = int( $size / 512 );
+ return ( $size, $blocks );
+ }
+ }
+ return ( undef, undef );
+}
+
+#---------------------------------------------------------------------
+# is_boot_partition()
# Accepts a partition hash and an optional mount list to avoid
# re-calling mount::list_mounted()
-sub is_boot_partition {
- my ( $part, $mount_list_ref ) = @_;
- return 1
- if ( $part->{'type'} eq 'freebsd-boot' or $part->{'type'} eq 'efi' );
- return 1 if ( $part->{'active'} );
- my @mounts = $mount_list_ref ? @$mount_list_ref : mount::list_mounted();
- foreach my $m (@mounts) {
- if ( $m->[1] eq '/boot' and $m->[0] eq $part->{'device'} ) {
- return 1;
- }
- }
- if ( $part->{'type'} eq 'freebsd-zfs' ) {
- my $out = backquote_command("zpool get bootfs 2>/dev/null");
- if ( $out =~ /\s+bootfs\s+\S+\/boot\s+/ ) {
- my $pool_out = backquote_command("zpool status 2>/dev/null");
- if ( $pool_out =~ /\Q$part->{'device'}\E/ ) {
- return 1;
- }
- }
- }
- return 0;
-}
-
-#---------------------------------------------------------------------
-# list_disks_partitions()
-# Returns a list of all disks, slices and partitions (optimized)
-sub list_disks_partitions {
- my @results;
- my %dev_stat_cache; # cache stat info per /dev device
- my @disk_devices;
-
- # Get disk devices from /dev directory
- if ( opendir( my $dh, "/dev" ) ) {
- my @all_devs = readdir($dh);
- closedir($dh);
- foreach my $dev (@all_devs) {
+sub is_boot_partition {
+ my ( $part, $mount_list_ref ) = @_;
+ return 1
+ if ( $part->{'type'} eq 'freebsd-boot' or $part->{'type'} eq 'efi' );
+ return 1 if ( $part->{'active'} );
+ my @mounts = $mount_list_ref ? @$mount_list_ref : mount::list_mounted();
+ foreach my $m (@mounts) {
+ if ( $m->[1] eq '/boot' and $m->[0] eq $part->{'device'} ) {
+ return 1;
+ }
+ }
+ if ( $part->{'type'} eq 'freebsd-zfs' ) {
+ my $out = backquote_command("zpool get bootfs 2>/dev/null");
+ if ( $out =~ /\s+bootfs\s+\S+\/boot\s+/ ) {
+ my $pool_out = backquote_command("zpool status 2>/dev/null");
+ if ( $pool_out =~ /\Q$part->{'device'}\E/ ) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+#---------------------------------------------------------------------
+# list_disks_partitions()
+# Returns a list of all disks, slices and partitions (optimized)
+sub list_disks_partitions {
+ my @results;
+ my %dev_stat_cache; # cache stat info per /dev device
+ my @disk_devices;
+
+ # Get disk devices from /dev directory
+ if ( opendir( my $dh, "/dev" ) ) {
+ my @all_devs = readdir($dh);
+ closedir($dh);
+ foreach my $dev (@all_devs) {
if ( $dev =~ /^(ada|ad|da|amrd|nvd|vtbd|nda)(\d+)$/ ) {
push( @disk_devices, $dev );
}
}
}
-
- # Fallback: sysctl
- if ( !@disk_devices ) {
- my $sysctl_out = backquote_command("sysctl -n kern.disks 2>/dev/null");
- if ($sysctl_out) {
- chomp($sysctl_out);
- @disk_devices = split( /\s+/, $sysctl_out );
- }
- }
-
- # Fallback: dmesg
- if ( !@disk_devices ) {
- my $dmesg_out = backquote_command(
+
+ # Fallback: sysctl
+ if ( !@disk_devices ) {
+ my $sysctl_out = backquote_command("sysctl -n kern.disks 2>/dev/null");
+ if ($sysctl_out) {
+ chomp($sysctl_out);
+ @disk_devices = split( /\s+/, $sysctl_out );
+ }
+ }
+
+ # Fallback: dmesg
+ if ( !@disk_devices ) {
+ my $dmesg_out = backquote_command(
"dmesg | grep -E '(ada|ad|da|amrd|nvd|vtbd|nda)[0-9]+:' 2>/dev/null"
);
while ( $dmesg_out =~ /\b(ada|ad|da|amrd|nvd|vtbd|nda)(\d+):/g ) {
@@ -180,275 +180,275 @@ sub list_disks_partitions {
push( @disk_devices, $disk ) if ( -e "/dev/$disk" );
}
}
-
- # Fallback: geom
- if ( !@disk_devices ) {
- my $geom_out = backquote_command("geom disk list 2>/dev/null");
- while ( $geom_out =~ /Name:\s+(\S+)/g ) {
- my $disk = $1;
- push( @disk_devices, $disk ) if ( -e "/dev/$disk" );
- }
- }
-
- # Get mount information once for all devices
- my ( $mount_info, $mount_list ) = get_all_mount_points_cached();
-
- foreach my $disk (@disk_devices) {
- my $disk_device = "/dev/$disk";
- my $diskinfo = { 'device' => $disk_device, 'name' => $disk };
-
- # Determine sector size once per disk (4K-aware)
- my $sectorsz = get_disk_sectorsize($disk_device) || 512;
- $diskinfo->{'sectorsize'} = $sectorsz;
-
- # Cache stat information for the disk device
- unless ( exists $dev_stat_cache{$disk_device} ) {
- my ( $size, $blocks ) = get_dev_stat($disk_device);
- $dev_stat_cache{$disk_device} =
- ( defined $size ) ? [ $size, $blocks || 0 ] : [ 0, 0 ];
- }
- my ( $size, $blocks_cached ) = @{ $dev_stat_cache{$disk_device} };
- if ( $size > 0 ) {
- $diskinfo->{'size'} = $size;
- $diskinfo->{'blocks'} = int( $size / $sectorsz );
- }
- else {
- my $diskinfo_out = backquote_command(
- "diskinfo " . quote_path($disk) . " 2>/dev/null" );
- if ( $diskinfo_out =~ /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.*)/ ) {
- $diskinfo->{'size'} = $1;
- $diskinfo->{'blocks'} = int( $1 / $sectorsz );
- $diskinfo->{'cylinders'} = $2;
- $diskinfo->{'heads'} = $3;
- $diskinfo->{'sectors'} = $4;
- if ( defined $5 && $5 ne '' && $5 !~ /^\d+$/ ) {
- $diskinfo->{'model'} = $5;
- }
- }
- if ( !$diskinfo->{'size'} ) {
- my $diskinfo_v_out = backquote_command(
- "diskinfo -v " . quote_path($disk) . " 2>/dev/null" );
- if ( $diskinfo_v_out =~ /sectorsize:\s*(\d+)/i ) {
- $sectorsz = $1;
- $diskinfo->{'sectorsize'} = $sectorsz;
- }
- if ( $diskinfo_v_out =~ /mediasize in bytes:\s+(\d+)/i ) {
- $diskinfo->{'size'} = $1;
- $diskinfo->{'blocks'} = int( $1 / $sectorsz );
- }
- if ( $diskinfo_v_out =~ /descr:\s+(.*)/ ) {
- $diskinfo->{'model'} = $1;
- }
- }
- if ( !$diskinfo->{'model'} ) {
- my $cam_id_out =
- backquote_command( "camcontrol identify "
- . quote_path($disk)
- . " 2>/dev/null" );
- if ( $cam_id_out =~ /model\s+(.*)/i ) {
- my $m = $1;
- $m =~ s/^\s+|\s+$//g;
- $diskinfo->{'model'} = $m;
- }
- }
- if ( !$diskinfo->{'model'} ) {
- my $inq_out =
- backquote_command( "camcontrol inquiry "
- . quote_path($disk)
- . " 2>/dev/null" );
- if ( $inq_out =~ /<([^>]+)>/ ) {
- $diskinfo->{'model'} = $1;
- }
- else {
- my ($vendor) =
- ( $inq_out =~ /Vendor:\s*(\S.*?)(?:\s{2,}|$)/i );
- my ($product) =
- ( $inq_out =~ /Product:\s*(\S.*?)(?:\s{2,}|$)/i );
- if ( $vendor || $product ) {
- $diskinfo->{'model'} = join( ' ',
- grep { defined && length } ( $vendor, $product ) );
- }
- }
- }
- if ( !$diskinfo->{'model'} ) {
- my $geom = get_detailed_disk_info($disk_device);
- if ( $geom && $geom->{'descr'} ) {
- $diskinfo->{'model'} = $geom->{'descr'};
- }
- elsif ( $geom && $geom->{'ident'} ) {
- $diskinfo->{'model'} = $geom->{'ident'};
- }
- }
-
- # If size still not known, try GEOM mediasize
- if ( !$diskinfo->{'size'} ) {
- my $geom2 = get_detailed_disk_info($disk_device);
- if ( $geom2 && $geom2->{'mediasize_bytes'} ) {
- $diskinfo->{'size'} = $geom2->{'mediasize_bytes'};
- $diskinfo->{'blocks'} = int( $diskinfo->{'size'} /
- ( $diskinfo->{'sectorsize'} || 512 ) );
- }
- }
- }
-
- # Determine disk type
- if ( $disk =~ /^ada/ or $disk =~ /^ad/ ) {
- $diskinfo->{'type'} = 'ide';
- }
- elsif ( $disk =~ /^da/ ) {
- $diskinfo->{'type'} = 'scsi';
- }
- elsif ( $disk =~ /^amrd/ ) {
- $diskinfo->{'type'} = 'memdisk';
- }
+
+ # Fallback: geom
+ if ( !@disk_devices ) {
+ my $geom_out = backquote_command("geom disk list 2>/dev/null");
+ while ( $geom_out =~ /Name:\s+(\S+)/g ) {
+ my $disk = $1;
+ push( @disk_devices, $disk ) if ( -e "/dev/$disk" );
+ }
+ }
+
+ # Get mount information once for all devices
+ my ( $mount_info, $mount_list ) = get_all_mount_points_cached();
+
+ foreach my $disk (@disk_devices) {
+ my $disk_device = "/dev/$disk";
+ my $diskinfo = { 'device' => $disk_device, 'name' => $disk };
+
+ # Determine sector size once per disk (4K-aware)
+ my $sectorsz = get_disk_sectorsize($disk_device) || 512;
+ $diskinfo->{'sectorsize'} = $sectorsz;
+
+ # Cache stat information for the disk device
+ unless ( exists $dev_stat_cache{$disk_device} ) {
+ my ( $size, $blocks ) = get_dev_stat($disk_device);
+ $dev_stat_cache{$disk_device} =
+ ( defined $size ) ? [ $size, $blocks || 0 ] : [ 0, 0 ];
+ }
+ my ( $size, $blocks_cached ) = @{ $dev_stat_cache{$disk_device} };
+ if ( $size > 0 ) {
+ $diskinfo->{'size'} = $size;
+ $diskinfo->{'blocks'} = int( $size / $sectorsz );
+ }
+ else {
+ my $diskinfo_out = backquote_command(
+ "diskinfo " . quote_path($disk) . " 2>/dev/null" );
+ if ( $diskinfo_out =~ /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.*)/ ) {
+ $diskinfo->{'size'} = $1;
+ $diskinfo->{'blocks'} = int( $1 / $sectorsz );
+ $diskinfo->{'cylinders'} = $2;
+ $diskinfo->{'heads'} = $3;
+ $diskinfo->{'sectors'} = $4;
+ if ( defined $5 && $5 ne '' && $5 !~ /^\d+$/ ) {
+ $diskinfo->{'model'} = $5;
+ }
+ }
+ if ( !$diskinfo->{'size'} ) {
+ my $diskinfo_v_out = backquote_command(
+ "diskinfo -v " . quote_path($disk) . " 2>/dev/null" );
+ if ( $diskinfo_v_out =~ /sectorsize:\s*(\d+)/i ) {
+ $sectorsz = $1;
+ $diskinfo->{'sectorsize'} = $sectorsz;
+ }
+ if ( $diskinfo_v_out =~ /mediasize in bytes:\s+(\d+)/i ) {
+ $diskinfo->{'size'} = $1;
+ $diskinfo->{'blocks'} = int( $1 / $sectorsz );
+ }
+ if ( $diskinfo_v_out =~ /descr:\s+(.*)/ ) {
+ $diskinfo->{'model'} = $1;
+ }
+ }
+ if ( !$diskinfo->{'model'} ) {
+ my $cam_id_out =
+ backquote_command( "camcontrol identify "
+ . quote_path($disk)
+ . " 2>/dev/null" );
+ if ( $cam_id_out =~ /model\s+(.*)/i ) {
+ my $m = $1;
+ $m =~ s/^\s+|\s+$//g;
+ $diskinfo->{'model'} = $m;
+ }
+ }
+ if ( !$diskinfo->{'model'} ) {
+ my $inq_out =
+ backquote_command( "camcontrol inquiry "
+ . quote_path($disk)
+ . " 2>/dev/null" );
+ if ( $inq_out =~ /<([^>]+)>/ ) {
+ $diskinfo->{'model'} = $1;
+ }
+ else {
+ my ($vendor) =
+ ( $inq_out =~ /Vendor:\s*(\S.*?)(?:\s{2,}|$)/i );
+ my ($product) =
+ ( $inq_out =~ /Product:\s*(\S.*?)(?:\s{2,}|$)/i );
+ if ( $vendor || $product ) {
+ $diskinfo->{'model'} = join( ' ',
+ grep { defined && length } ( $vendor, $product ) );
+ }
+ }
+ }
+ if ( !$diskinfo->{'model'} ) {
+ my $geom = get_detailed_disk_info($disk_device);
+ if ( $geom && $geom->{'descr'} ) {
+ $diskinfo->{'model'} = $geom->{'descr'};
+ }
+ elsif ( $geom && $geom->{'ident'} ) {
+ $diskinfo->{'model'} = $geom->{'ident'};
+ }
+ }
+
+ # If size still not known, try GEOM mediasize
+ if ( !$diskinfo->{'size'} ) {
+ my $geom2 = get_detailed_disk_info($disk_device);
+ if ( $geom2 && $geom2->{'mediasize_bytes'} ) {
+ $diskinfo->{'size'} = $geom2->{'mediasize_bytes'};
+ $diskinfo->{'blocks'} = int( $diskinfo->{'size'} /
+ ( $diskinfo->{'sectorsize'} || 512 ) );
+ }
+ }
+ }
+
+ # Determine disk type
+ if ( $disk =~ /^ada/ or $disk =~ /^ad/ ) {
+ $diskinfo->{'type'} = 'ide';
+ }
+ elsif ( $disk =~ /^da/ ) {
+ $diskinfo->{'type'} = 'scsi';
+ }
+ elsif ( $disk =~ /^amrd/ ) {
+ $diskinfo->{'type'} = 'memdisk';
+ }
elsif ( $disk =~ /^(?:nvd|nda)/ ) {
$diskinfo->{'type'} = 'nvme';
}
- elsif ( $disk =~ /^vtbd/ ) {
- $diskinfo->{'type'} = 'virtio';
- }
-
- # Process slices and partitions
- $diskinfo->{'slices'} = [];
- if ( has_command("gpart") ) {
- my $gpart_out = backquote_command(
- "gpart show " . quote_path($disk) . " 2>/dev/null" );
- my @lines = split( /\n/, $gpart_out );
- my $in_disk = 0;
- my $disk_scheme = undef; # GPT, MBR, etc.
- foreach my $line (@lines) {
- if ( $line =~ /=>/ ) {
- $in_disk = 1;
-
- # Try to extract scheme from header line
- if ( $line =~ /=>.*?\b$disk\b\s+(\S+)/ ) {
- $disk_scheme = $1;
- }
- next;
- }
- if ( $in_disk and $line =~ /^\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/ )
- {
- my ( $start, $num_blocks, $name_or_idx, $raw_type ) =
- ( $1, $2, $3, $4 );
- next if ( $name_or_idx eq '-' or $raw_type eq 'free' );
- my $slice_type =
- ( $raw_type eq '-' ) ? "freebsd" : $raw_type;
-
- # Determine slice index and device name
- my ( $slice_index, $slice_devname );
- if ( $name_or_idx =~ /^$disk(?:p|s)(\d+)$/ ) {
- $slice_index = $1;
- $slice_devname = $name_or_idx;
- }
- elsif ( $name_or_idx =~ /^\d+$/ ) {
- $slice_index = $name_or_idx;
- my $sep =
- ( defined $disk_scheme && $disk_scheme =~ /GPT/i )
- ? 'p'
- : 's';
- $slice_devname = $disk . $sep . $slice_index;
- }
- else {
- # Fallback: use as provided
- $slice_devname = $name_or_idx;
-
- # Try to extract index from suffix if possible
- ($slice_index) = ( $slice_devname =~ /(?:p|s)(\d+)$/ );
- $slice_index ||= $name_or_idx;
- }
- my $slice_device = "/dev/$slice_devname";
-
- my $slice = {
- 'number' => $slice_index,
- 'startblock' => $start,
- 'blocks' => $num_blocks,
- 'size' => $num_blocks * $sectorsz,
- 'type' => $slice_type,
- 'device' => $slice_device,
- 'parts' => []
- };
- $slice->{'used'} = $mount_info{$slice_device};
-
- # Get partitions for this slice once, using the correct provider name
- my $gpart_slice_out =
- backquote_command( "gpart show "
- . quote_path($slice_devname)
- . " 2>/dev/null" );
- my @slice_lines = split( /\n/, $gpart_slice_out );
- my $in_slice = 0;
- my $slice_scheme;
- foreach my $slice_line (@slice_lines) {
- if ( $slice_line =~ /=>.*?\s+$slice_devname\s+(\S+)/ ) {
- $in_slice = 1;
- $slice_scheme = $1; # e.g., BSD, GPT
- next;
- }
- if ( $in_slice
- and $slice_line =~
- /^\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/ )
- {
- my (
- $p_start, $p_blocks,
- $part_idx_or_name, $raw_ptype
- ) = ( $1, $2, $3, $4 );
- next
- if ( $part_idx_or_name eq '-'
- or $raw_ptype eq 'free' );
- my $part_type =
- ( $raw_ptype eq '-' and $part_idx_or_name ne '-' )
- ? "freebsd-ufs"
- : $raw_ptype;
-
- # For BSD disklabel, third column is index (1-based), convert to letter
- my ( $part_letter, $part_device );
- if ( $slice_scheme
- && $slice_scheme =~ /BSD/i
- && $part_idx_or_name =~ /^\d+$/ )
- {
- my $idx = int($part_idx_or_name);
- $part_letter = chr( ord('a') + $idx - 1 )
- ; # 1 -> 'a', 2 -> 'b', etc.
- $part_device = $slice_device . $part_letter;
- }
- else {
- # For other schemes or if already a name
- $part_device = "/dev/$part_idx_or_name";
- $part_letter = substr( $part_idx_or_name, -1 );
- }
- my $part = {
- 'letter' => $part_letter,
- 'startblock' => $p_start,
- 'blocks' => $p_blocks,
- 'size' => $p_blocks * $sectorsz,
- 'type' => $part_type,
- 'device' => $part_device,
- };
- $part->{'used'} = $mount_info{$part_device};
- push( @{ $slice->{'parts'} }, $part );
- }
- }
- push( @{ $diskinfo->{'slices'} }, $slice );
- }
- }
- }
- else {
+ elsif ( $disk =~ /^vtbd/ ) {
+ $diskinfo->{'type'} = 'virtio';
+ }
+
+ # Process slices and partitions
+ $diskinfo->{'slices'} = [];
+ if ( has_command("gpart") ) {
+ my $gpart_out = backquote_command(
+ "gpart show " . quote_path($disk) . " 2>/dev/null" );
+ my @lines = split( /\n/, $gpart_out );
+ my $in_disk = 0;
+ my $disk_scheme = undef; # GPT, MBR, etc.
+ foreach my $line (@lines) {
+ if ( $line =~ /=>/ ) {
+ $in_disk = 1;
+
+ # Try to extract scheme from header line
+ if ( $line =~ /=>.*?\b$disk\b\s+(\S+)/ ) {
+ $disk_scheme = $1;
+ }
+ next;
+ }
+ if ( $in_disk and $line =~ /^\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/ )
+ {
+ my ( $start, $num_blocks, $name_or_idx, $raw_type ) =
+ ( $1, $2, $3, $4 );
+ next if ( $name_or_idx eq '-' or $raw_type eq 'free' );
+ my $slice_type =
+ ( $raw_type eq '-' ) ? "freebsd" : $raw_type;
+
+ # Determine slice index and device name
+ my ( $slice_index, $slice_devname );
+ if ( $name_or_idx =~ /^$disk(?:p|s)(\d+)$/ ) {
+ $slice_index = $1;
+ $slice_devname = $name_or_idx;
+ }
+ elsif ( $name_or_idx =~ /^\d+$/ ) {
+ $slice_index = $name_or_idx;
+ my $sep =
+ ( defined $disk_scheme && $disk_scheme =~ /GPT/i )
+ ? 'p'
+ : 's';
+ $slice_devname = $disk . $sep . $slice_index;
+ }
+ else {
+ # Fallback: use as provided
+ $slice_devname = $name_or_idx;
+
+ # Try to extract index from suffix if possible
+ ($slice_index) = ( $slice_devname =~ /(?:p|s)(\d+)$/ );
+ $slice_index ||= $name_or_idx;
+ }
+ my $slice_device = "/dev/$slice_devname";
+
+ my $slice = {
+ 'number' => $slice_index,
+ 'startblock' => $start,
+ 'blocks' => $num_blocks,
+ 'size' => $num_blocks * $sectorsz,
+ 'type' => $slice_type,
+ 'device' => $slice_device,
+ 'parts' => []
+ };
+ $slice->{'used'} = $mount_info{$slice_device};
+
+ # Get partitions for this slice once, using the correct provider name
+ my $gpart_slice_out =
+ backquote_command( "gpart show "
+ . quote_path($slice_devname)
+ . " 2>/dev/null" );
+ my @slice_lines = split( /\n/, $gpart_slice_out );
+ my $in_slice = 0;
+ my $slice_scheme;
+ foreach my $slice_line (@slice_lines) {
+ if ( $slice_line =~ /=>.*?\s+$slice_devname\s+(\S+)/ ) {
+ $in_slice = 1;
+ $slice_scheme = $1; # e.g., BSD, GPT
+ next;
+ }
+ if ( $in_slice
+ and $slice_line =~
+ /^\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/ )
+ {
+ my (
+ $p_start, $p_blocks,
+ $part_idx_or_name, $raw_ptype
+ ) = ( $1, $2, $3, $4 );
+ next
+ if ( $part_idx_or_name eq '-'
+ or $raw_ptype eq 'free' );
+ my $part_type =
+ ( $raw_ptype eq '-' and $part_idx_or_name ne '-' )
+ ? "freebsd-ufs"
+ : $raw_ptype;
+
+ # For BSD disklabel, third column is index (1-based), convert to letter
+ my ( $part_letter, $part_device );
+ if ( $slice_scheme
+ && $slice_scheme =~ /BSD/i
+ && $part_idx_or_name =~ /^\d+$/ )
+ {
+ my $idx = int($part_idx_or_name);
+ $part_letter = chr( ord('a') + $idx - 1 )
+ ; # 1 -> 'a', 2 -> 'b', etc.
+ $part_device = $slice_device . $part_letter;
+ }
+ else {
+ # For other schemes or if already a name
+ $part_device = "/dev/$part_idx_or_name";
+ $part_letter = substr( $part_idx_or_name, -1 );
+ }
+ my $part = {
+ 'letter' => $part_letter,
+ 'startblock' => $p_start,
+ 'blocks' => $p_blocks,
+ 'size' => $p_blocks * $sectorsz,
+ 'type' => $part_type,
+ 'device' => $part_device,
+ };
+ $part->{'used'} = $mount_info{$part_device};
+ push( @{ $slice->{'parts'} }, $part );
+ }
+ }
+ push( @{ $diskinfo->{'slices'} }, $slice );
+ }
+ }
+ }
+ else {
# If no slices found with gpart, use fdisk if available
# (similar caching ideas apply)
- if ( has_command("fdisk") ) {
- my $fdisk_out = backquote_command(
- "fdisk " . quote_path("/dev/$disk") . " 2>/dev/null" );
- foreach my $line ( split( /\n/, $fdisk_out ) ) {
- if ( $line =~ /^\s*(\d+):\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/ )
- {
- my $slice_device = "/dev/${disk}s$1";
- my $slice = {
- 'number' => $1,
- 'startblock' => $2,
- 'blocks' => ( $4 - $2 + 1 ),
- 'size' => ( $4 - $2 + 1 ) * $sectorsz,
- 'type' => $5,
- 'device' => $slice_device,
- 'parts' => []
+ if ( has_command("fdisk") ) {
+ my $fdisk_out = backquote_command(
+ "fdisk " . quote_path("/dev/$disk") . " 2>/dev/null" );
+ foreach my $line ( split( /\n/, $fdisk_out ) ) {
+ if ( $line =~ /^\s*(\d+):\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/ )
+ {
+ my $slice_device = "/dev/${disk}s$1";
+ my $slice = {
+ 'number' => $1,
+ 'startblock' => $2,
+ 'blocks' => ( $4 - $2 + 1 ),
+ 'size' => ( $4 - $2 + 1 ) * $sectorsz,
+ 'type' => $5,
+ 'device' => $slice_device,
+ 'parts' => []
};
$slice->{'used'} = $mount_info{$slice_device};
my $dlcmd = _disklabel_cmd();
@@ -463,94 +463,94 @@ sub list_disks_partitions {
if ( $label_line =~
/^(\s*)([a-h]):\s+(\d+)\s+(\d+)\s+(\S+)/ )
{
- my $part_device = "${slice_device}$2";
- my $part = {
- 'letter' => $2,
- 'startblock' => $3,
- 'blocks' => $4,
- 'size' => $4 * $sectorsz,
- 'type' => $5,
- 'device' => $part_device
- };
- $part->{'used'} = $mount_info{$part_device};
- push( @{ $slice->{'parts'} }, $part );
- }
- }
- push( @{ $diskinfo->{'slices'} }, $slice );
- }
- }
- }
- }
-
- # If size was not determined, estimate from slices if available
- if ( !$diskinfo->{'size'} and @{ $diskinfo->{'slices'} } ) {
- my $total_size = 0;
- $total_size += $_->{'size'} for @{ $diskinfo->{'slices'} };
- if ( $total_size > 0 ) {
- $diskinfo->{'size'} = $total_size;
- $diskinfo->{'blocks'} =
- int( $total_size / ( $diskinfo->{'sectorsize'} || 512 ) );
- }
- }
-
- # Finally, add this disk (dummy size set if necessary)
- if ( $diskinfo->{'size'} or -e $disk_device ) {
- $diskinfo->{'size'} = $diskinfo->{'size'} || 0;
- $diskinfo->{'blocks'} = $diskinfo->{'blocks'} || 0;
- push( @results, $diskinfo );
- }
- }
- return @results;
-}
-
-# check_fdisk() unchanged
-sub check_fdisk {
- if ( !has_command("fdisk") and !has_command("gpart") ) {
- return text( 'index_efdisk', "fdisk", "gpart" );
- }
- return undef;
-}
-
-# is_using_gpart()
-sub is_using_gpart {
- return has_command("gpart") ? 1 : 0;
-}
-
-# disk_name(device) extracts name from /dev/device
-sub disk_name {
- my ($device) = @_;
- $device =~ s/^\/dev\///;
- return $device;
-}
-
-# slice_name(slice)
-sub slice_name {
- my ($slice) = @_;
- if ( $slice->{'device'} =~ /\/dev\/(\S+)/ ) {
- return $1;
- }
- return $slice->{'number'};
-}
-
-# slice_number(slice)
-sub slice_number {
- my ($slice) = @_;
- if ( $slice->{'device'} =~ /\/dev\/\S+s(\d+)/ ) {
- return $1;
- }
- if ( $slice->{'device'} =~ /\/dev\/\S+p(\d+)/ ) {
- return $1;
- }
- return $slice->{'number'};
-}
-
-sub _safe_uint {
- my ($v) = @_;
- return undef unless defined $v;
- return undef unless $v =~ /^\d+$/;
- return int($v);
-}
-
+ my $part_device = "${slice_device}$2";
+ my $part = {
+ 'letter' => $2,
+ 'startblock' => $3,
+ 'blocks' => $4,
+ 'size' => $4 * $sectorsz,
+ 'type' => $5,
+ 'device' => $part_device
+ };
+ $part->{'used'} = $mount_info{$part_device};
+ push( @{ $slice->{'parts'} }, $part );
+ }
+ }
+ push( @{ $diskinfo->{'slices'} }, $slice );
+ }
+ }
+ }
+ }
+
+ # If size was not determined, estimate from slices if available
+ if ( !$diskinfo->{'size'} and @{ $diskinfo->{'slices'} } ) {
+ my $total_size = 0;
+ $total_size += $_->{'size'} for @{ $diskinfo->{'slices'} };
+ if ( $total_size > 0 ) {
+ $diskinfo->{'size'} = $total_size;
+ $diskinfo->{'blocks'} =
+ int( $total_size / ( $diskinfo->{'sectorsize'} || 512 ) );
+ }
+ }
+
+ # Finally, add this disk (dummy size set if necessary)
+ if ( $diskinfo->{'size'} or -e $disk_device ) {
+ $diskinfo->{'size'} = $diskinfo->{'size'} || 0;
+ $diskinfo->{'blocks'} = $diskinfo->{'blocks'} || 0;
+ push( @results, $diskinfo );
+ }
+ }
+ return @results;
+}
+
+# check_fdisk() unchanged
+sub check_fdisk {
+ if ( !has_command("fdisk") and !has_command("gpart") ) {
+ return text( 'index_efdisk', "fdisk", "gpart" );
+ }
+ return undef;
+}
+
+# is_using_gpart()
+sub is_using_gpart {
+ return has_command("gpart") ? 1 : 0;
+}
+
+# disk_name(device) extracts name from /dev/device
+sub disk_name {
+ my ($device) = @_;
+ $device =~ s/^\/dev\///;
+ return $device;
+}
+
+# slice_name(slice)
+sub slice_name {
+ my ($slice) = @_;
+ if ( $slice->{'device'} =~ /\/dev\/(\S+)/ ) {
+ return $1;
+ }
+ return $slice->{'number'};
+}
+
+# slice_number(slice)
+sub slice_number {
+ my ($slice) = @_;
+ if ( $slice->{'device'} =~ /\/dev\/\S+s(\d+)/ ) {
+ return $1;
+ }
+ if ( $slice->{'device'} =~ /\/dev\/\S+p(\d+)/ ) {
+ return $1;
+ }
+ return $slice->{'number'};
+}
+
+sub _safe_uint {
+ my ($v) = @_;
+ return undef unless defined $v;
+ return undef unless $v =~ /^\d+$/;
+ return int($v);
+}
+
sub _safe_letter {
my ($v) = @_;
return undef unless defined $v;
@@ -684,65 +684,65 @@ sub _disklabel_update_partition_line {
#---------------------------------------------------------------------
# Filesystem command generation and slice/partition modification functions
sub create_slice {
- my ( $disk, $slice ) = @_;
- my $cmd;
- if ( is_using_gpart() ) {
-
+ my ( $disk, $slice ) = @_;
+ my $cmd;
+ if ( is_using_gpart() ) {
+
# Ensure a partitioning scheme exists (default to MBR for non-GPT,
# GPT if new) before adding
- my $base = disk_name( $disk->{'device'} );
- my $ds = get_disk_structure($base);
- my $scheme =
- 'MBR'; # default for existing disks or when type suggests MBR
- if ( !$ds || !$ds->{'scheme'} ) {
-
- # No scheme exists - decide based on partition type
- if ( $slice->{'type'} =~ /^(freebsd|fat32|ntfs|linux)$/i ) {
- $scheme = 'MBR';
- }
- else {
- $scheme = 'GPT';
- }
- my $init = "gpart create -s $scheme " . quote_path($base);
- my $init_out = backquote_command("$init 2>&1");
- if ( $? != 0 && $init_out !~ /File exists|already exists/i ) {
- return $init_out;
- }
-
- # Refresh disk structure after creation
- $ds = get_disk_structure($base);
- }
- else {
- $scheme = $ds->{'scheme'};
- }
-
- # Defense-in-depth: validate type against allowed list for scheme
- my %allowed =
- map { $_->[0] => 1 } list_partition_types($scheme);
- return $text{'nslice_etype'}
- unless ( defined $slice->{'type'} && $allowed{ $slice->{'type'} } );
-
- $cmd = "gpart add -t " . $slice->{'type'};
- if ( defined $slice->{'startblock'}
- && $slice->{'startblock'} =~ /^\d+$/ )
- {
- $cmd .= " -b " . int( $slice->{'startblock'} );
- }
- if ( defined $slice->{'blocks'} && $slice->{'blocks'} =~ /^\d+$/ ) {
- $cmd .= " -s " . int( $slice->{'blocks'} );
- }
- $cmd .= " " . quote_path($base);
- my $out = backquote_command("$cmd 2>&1");
- if ($?) {
- return $out;
- }
-
- # After successful creation, populate the device field for the slice
- # Determine the separator based on scheme
- my $sep = ( $scheme =~ /GPT/i ) ? 'p' : 's';
- my $slice_num = $slice->{'number'};
- $slice->{'device'} = "/dev/${base}${sep}${slice_num}";
- return undef;
+ my $base = disk_name( $disk->{'device'} );
+ my $ds = get_disk_structure($base);
+ my $scheme =
+ 'MBR'; # default for existing disks or when type suggests MBR
+ if ( !$ds || !$ds->{'scheme'} ) {
+
+ # No scheme exists - decide based on partition type
+ if ( $slice->{'type'} =~ /^(freebsd|fat32|ntfs|linux)$/i ) {
+ $scheme = 'MBR';
+ }
+ else {
+ $scheme = 'GPT';
+ }
+ my $init = "gpart create -s $scheme " . quote_path($base);
+ my $init_out = backquote_command("$init 2>&1");
+ if ( $? != 0 && $init_out !~ /File exists|already exists/i ) {
+ return $init_out;
+ }
+
+ # Refresh disk structure after creation
+ $ds = get_disk_structure($base);
+ }
+ else {
+ $scheme = $ds->{'scheme'};
+ }
+
+ # Defense-in-depth: validate type against allowed list for scheme
+ my %allowed =
+ map { $_->[0] => 1 } list_partition_types($scheme);
+ return $text{'nslice_etype'}
+ unless ( defined $slice->{'type'} && $allowed{ $slice->{'type'} } );
+
+ $cmd = "gpart add -t " . $slice->{'type'};
+ if ( defined $slice->{'startblock'}
+ && $slice->{'startblock'} =~ /^\d+$/ )
+ {
+ $cmd .= " -b " . int( $slice->{'startblock'} );
+ }
+ if ( defined $slice->{'blocks'} && $slice->{'blocks'} =~ /^\d+$/ ) {
+ $cmd .= " -s " . int( $slice->{'blocks'} );
+ }
+ $cmd .= " " . quote_path($base);
+ my $out = backquote_command("$cmd 2>&1");
+ if ($?) {
+ return $out;
+ }
+
+ # After successful creation, populate the device field for the slice
+ # Determine the separator based on scheme
+ my $sep = ( $scheme =~ /GPT/i ) ? 'p' : 's';
+ my $slice_num = $slice->{'number'};
+ $slice->{'device'} = "/dev/${base}${sep}${slice_num}";
+ return undef;
}
else {
my %allowed = map { $_ => 1 } fdisk::list_tags();
@@ -771,23 +771,23 @@ sub create_slice {
return undef;
}
}
-
-sub delete_slice {
- my ( $disk, $slice ) = @_;
- if ( is_boot_partition($slice) ) { return $text{'slice_eboot'}; }
- foreach my $p ( @{ $slice->{'parts'} } ) {
- if ( is_boot_partition($p) ) { return $text{'slice_eboot'}; }
- }
- my $cmd;
+
+sub delete_slice {
+ my ( $disk, $slice ) = @_;
+ if ( is_boot_partition($slice) ) { return $text{'slice_eboot'}; }
+ foreach my $p ( @{ $slice->{'parts'} } ) {
+ if ( is_boot_partition($p) ) { return $text{'slice_eboot'}; }
+ }
+ my $cmd;
if ( is_using_gpart() ) {
- my $idx = _safe_uint( slice_number($slice) );
- return $text{'slice_egone'} unless defined $idx;
- $cmd =
- "gpart delete -i "
- . $idx . " "
- . quote_path( disk_name( $disk->{'device'} ) );
- my $out = backquote_command("$cmd 2>&1");
- return ($?) ? $out : undef;
+ my $idx = _safe_uint( slice_number($slice) );
+ return $text{'slice_egone'} unless defined $idx;
+ $cmd =
+ "gpart delete -i "
+ . $idx . " "
+ . quote_path( disk_name( $disk->{'device'} ) );
+ my $out = backquote_command("$cmd 2>&1");
+ return ($?) ? $out : undef;
}
else {
my $sn = _safe_uint( $slice->{'number'} );
@@ -801,20 +801,20 @@ sub delete_slice {
return undef;
}
}
-
-sub delete_partition {
- my ( $disk, $slice, $part ) = @_;
- if ( is_boot_partition($part) ) { return $text{'part_eboot'}; }
- my $cmd;
- if ( is_using_gpart() ) {
-
- # BSD disklabel uses 1-based indexing: 'a' = 1, 'b' = 2, etc.
- my $pl = _safe_letter( $part->{'letter'} );
- return $text{'part_egone'} unless defined $pl;
- my $idx = ( ord($pl) - ord('a') ) + 1;
- $cmd = "gpart delete -i $idx " . quote_path( slice_name($slice) );
- my $out = backquote_command("$cmd 2>&1");
- return ($?) ? $out : undef;
+
+sub delete_partition {
+ my ( $disk, $slice, $part ) = @_;
+ if ( is_boot_partition($part) ) { return $text{'part_eboot'}; }
+ my $cmd;
+ if ( is_using_gpart() ) {
+
+ # BSD disklabel uses 1-based indexing: 'a' = 1, 'b' = 2, etc.
+ my $pl = _safe_letter( $part->{'letter'} );
+ return $text{'part_egone'} unless defined $pl;
+ my $idx = ( ord($pl) - ord('a') ) + 1;
+ $cmd = "gpart delete -i $idx " . quote_path( slice_name($slice) );
+ my $out = backquote_command("$cmd 2>&1");
+ return ($?) ? $out : undef;
}
else {
my $pl = _safe_letter( $part->{'letter'} );
@@ -834,32 +834,32 @@ sub delete_partition {
return undef;
}
}
-
+
sub modify_slice {
my ( $disk, $oldslice, $slice, $part ) = @_;
if ( is_boot_partition($slice) ) { return $text{'slice_eboot'}; }
foreach my $p ( @{ $slice->{'parts'} } ) {
if ( is_boot_partition($p) ) { return $text{'slice_eboot'}; }
}
- my $cmd;
+ my $cmd;
if ( is_using_gpart() ) {
- my $base = disk_name( $disk->{'device'} );
- my $ds = get_disk_structure($base);
- my $scheme = ( $ds && $ds->{'scheme'} ) ? $ds->{'scheme'} : 'GPT';
- my %allowed =
- map { $_->[0] => 1 } list_partition_types($scheme);
- return $text{'nslice_etype'}
- unless ( defined $slice->{'type'} && $allowed{ $slice->{'type'} } );
-
- my $idx = _safe_uint( slice_number($slice) );
- return $text{'slice_egone'} unless defined $idx;
- $cmd =
- "gpart modify -i "
- . $idx . " -t "
- . $slice->{'type'} . " "
- . quote_path($base);
- my $out = backquote_command("$cmd 2>&1");
- return ($?) ? $out : undef;
+ my $base = disk_name( $disk->{'device'} );
+ my $ds = get_disk_structure($base);
+ my $scheme = ( $ds && $ds->{'scheme'} ) ? $ds->{'scheme'} : 'GPT';
+ my %allowed =
+ map { $_->[0] => 1 } list_partition_types($scheme);
+ return $text{'nslice_etype'}
+ unless ( defined $slice->{'type'} && $allowed{ $slice->{'type'} } );
+
+ my $idx = _safe_uint( slice_number($slice) );
+ return $text{'slice_egone'} unless defined $idx;
+ $cmd =
+ "gpart modify -i "
+ . $idx . " -t "
+ . $slice->{'type'} . " "
+ . quote_path($base);
+ my $out = backquote_command("$cmd 2>&1");
+ return ($?) ? $out : undef;
}
else {
my %allowed = map { $_ => 1 } fdisk::list_tags();
@@ -887,42 +887,42 @@ sub modify_slice {
return undef;
}
}
-
-sub save_partition {
- my ( $disk, $slice, $part ) = @_;
- my $cmd;
- if ( is_using_gpart() ) {
- my $provider = slice_name($slice);
-
- # Detect if this provider is a BSD label (sub-partitions) or GPT/MBR
- my $show =
- backquote_command( "gpart show " . quote_path($provider) . " 2>&1" );
- if ( $show =~ /\bBSD\b/ ) {
-
+
+sub save_partition {
+ my ( $disk, $slice, $part ) = @_;
+ my $cmd;
+ if ( is_using_gpart() ) {
+ my $provider = slice_name($slice);
+
+ # Detect if this provider is a BSD label (sub-partitions) or GPT/MBR
+ my $show =
+ backquote_command( "gpart show " . quote_path($provider) . " 2>&1" );
+ if ( $show =~ /\bBSD\b/ ) {
+
# Inner BSD label: index is 1-based a->1, b->2, etc. Only
# FreeBSD partition types are valid here.
- my $pl = _safe_letter( $part->{'letter'} );
- return $text{'part_egone'} unless defined $pl;
- my $idx = ( ord($pl) - ord('a') ) + 1;
- my %allowed =
- map { $_->[0] => 1 } list_partition_types('BSD');
- return $text{'part_etype'}
- unless ( defined $part->{'type'} && $allowed{ $part->{'type'} } );
- $cmd =
- "gpart modify -i $idx -t "
- . $part->{'type'} . " "
- . quote_path($provider);
- }
- else {
+ my $pl = _safe_letter( $part->{'letter'} );
+ return $text{'part_egone'} unless defined $pl;
+ my $idx = ( ord($pl) - ord('a') ) + 1;
+ my %allowed =
+ map { $_->[0] => 1 } list_partition_types('BSD');
+ return $text{'part_etype'}
+ unless ( defined $part->{'type'} && $allowed{ $part->{'type'} } );
+ $cmd =
+ "gpart modify -i $idx -t "
+ . $part->{'type'} . " "
+ . quote_path($provider);
+ }
+ else {
# Not a BSD label; modifying a top-level partition by letter is
# invalid. Return an error with guidance.
- return
+ return
"Invalid operation: attempting to modify non-BSD sub-partition "
. "by letter. Use slice editing for top-level partitions.";
- }
- my $out = backquote_command("$cmd 2>&1");
- return ($?) ? $out : undef;
- }
+ }
+ my $out = backquote_command("$cmd 2>&1");
+ return ($?) ? $out : undef;
+ }
else {
my $pl = _safe_letter( $part->{'letter'} );
return $text{'part_egone'} unless defined $pl;
@@ -948,8 +948,8 @@ sub save_partition {
return undef;
}
}
-
-# Create a new BSD partition inside an MBR slice (gpart BSD label)
+
+# Create a new BSD partition inside an MBR slice (gpart BSD label)
sub create_partition {
my ( $disk, $slice, $part ) = @_;
@@ -1037,34 +1037,34 @@ sub create_partition {
$part->{'device'} = $slice->{'device'} . $pl;
return undef;
}
-
-sub get_create_filesystem_command {
- my ( $disk, $slice, $part, $options ) = @_;
- my $device = $part ? $part->{'device'} : $slice->{'device'};
- my @cmd = ("newfs");
- if ( defined $options->{'free'} && $options->{'free'} =~ /^\d+$/ ) {
- push( @cmd, "-m", $options->{'free'} );
- }
- if ( defined $options->{'label'} && length $options->{'label'} ) {
- push( @cmd, "-L", quote_path( $options->{'label'} ) );
- }
- push( @cmd, "-t" ) if ( $options->{'trim'} );
- push( @cmd, quote_path($device) );
- return join( " ", @cmd );
-}
-
-# Helper to set a GPT or BSD partition label after filesystem creation
-sub set_partition_label {
- my (%args) = @_;
- my $disk = $args{'disk'};
- my $slice = $args{'slice'};
- my $part = $args{'part'}; # optional
- my $label = $args{'label'};
- return if ( !defined $label || $label eq '' );
- my $base = $disk->{'device'};
- $base =~ s{^/dev/}{};
- my $ds = get_disk_structure($base);
-
+
+sub get_create_filesystem_command {
+ my ( $disk, $slice, $part, $options ) = @_;
+ my $device = $part ? $part->{'device'} : $slice->{'device'};
+ my @cmd = ("newfs");
+ if ( defined $options->{'free'} && $options->{'free'} =~ /^\d+$/ ) {
+ push( @cmd, "-m", $options->{'free'} );
+ }
+ if ( defined $options->{'label'} && length $options->{'label'} ) {
+ push( @cmd, "-L", quote_path( $options->{'label'} ) );
+ }
+ push( @cmd, "-t" ) if ( $options->{'trim'} );
+ push( @cmd, quote_path($device) );
+ return join( " ", @cmd );
+}
+
+# Helper to set a GPT or BSD partition label after filesystem creation
+sub set_partition_label {
+ my (%args) = @_;
+ my $disk = $args{'disk'};
+ my $slice = $args{'slice'};
+ my $part = $args{'part'}; # optional
+ my $label = $args{'label'};
+ return if ( !defined $label || $label eq '' );
+ my $base = $disk->{'device'};
+ $base =~ s{^/dev/}{};
+ my $ds = get_disk_structure($base);
+
# GPT: label at disk level via gpart modify
if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
my $idx =
@@ -1074,41 +1074,41 @@ sub set_partition_label {
"gpart modify -i $idx -l "
. quote_path($label) . " "
. quote_path($base);
- my $out = backquote_command("$cmd 2>&1");
- return ( $? ? $out : undef );
- }
- return undef;
- }
-
- # MBR: use glabel for slice-level or partition-level labels
- my $device = $part ? $part->{'device'} : $slice->{'device'};
- if ( $device && has_command('glabel') ) {
-
- # Remove existing glabel if present, then add new one
- my $existing = backquote_command(
- "glabel status 2>/dev/null | grep " . quote_path($device) );
- if ( $existing =~ /^(\S+)\s+/ ) {
- my $old_label = $1;
- my $destroy_out = backquote_command(
- "glabel destroy " . quote_path($old_label) . " 2>&1" );
- }
- my $cmd =
- "glabel label " . quote_path($label) . " " . quote_path($device);
- my $out = backquote_command("$cmd 2>&1");
- return ( $? ? $out : undef );
- }
- return undef;
-}
-
-sub remove_partition_label {
- my (%args) = @_;
- my $disk = $args{'disk'};
- my $slice = $args{'slice'};
- my $part = $args{'part'};
- my $base = $disk->{'device'};
- $base =~ s{^/dev/}{};
- my $ds = get_disk_structure($base);
-
+ my $out = backquote_command("$cmd 2>&1");
+ return ( $? ? $out : undef );
+ }
+ return undef;
+ }
+
+ # MBR: use glabel for slice-level or partition-level labels
+ my $device = $part ? $part->{'device'} : $slice->{'device'};
+ if ( $device && has_command('glabel') ) {
+
+ # Remove existing glabel if present, then add new one
+ my $existing = backquote_command(
+ "glabel status 2>/dev/null | grep " . quote_path($device) );
+ if ( $existing =~ /^(\S+)\s+/ ) {
+ my $old_label = $1;
+ my $destroy_out = backquote_command(
+ "glabel destroy " . quote_path($old_label) . " 2>&1" );
+ }
+ my $cmd =
+ "glabel label " . quote_path($label) . " " . quote_path($device);
+ my $out = backquote_command("$cmd 2>&1");
+ return ( $? ? $out : undef );
+ }
+ return undef;
+}
+
+sub remove_partition_label {
+ my (%args) = @_;
+ my $disk = $args{'disk'};
+ my $slice = $args{'slice'};
+ my $part = $args{'part'};
+ my $base = $disk->{'device'};
+ $base =~ s{^/dev/}{};
+ my $ds = get_disk_structure($base);
+
# GPT: remove label via gpart modify -l ""
if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
my $idx = $part ? undef : _safe_uint( $slice->{'number'} );
@@ -1117,162 +1117,166 @@ sub remove_partition_label {
my $out = backquote_command("$cmd 2>&1");
return ( $? ? $out : undef );
}
- return undef;
- }
-
- # MBR: remove glabel
- my $device = $part ? $part->{'device'} : $slice->{'device'};
- if ( $device && has_command('glabel') ) {
- my $existing = backquote_command(
- "glabel status 2>/dev/null | grep " . quote_path($device) );
+ return undef;
+ }
+
+ # MBR: remove glabel
+ my $device = $part ? $part->{'device'} : $slice->{'device'};
+ if ( $device && has_command('glabel') ) {
+ my $existing = backquote_command(
+ "glabel status 2>/dev/null | grep " . quote_path($device) );
if ( $existing =~ /^(\S+)\s+/ ) {
my $label = $1;
my $cmd = "glabel destroy " . quote_path($label);
my $out = backquote_command("$cmd 2>&1");
return ( $? ? $out : undef );
}
- }
- return undef;
-}
-
-# Get the current label for a slice/partition device (GPT label or glabel)
-sub get_device_label_name {
- my (%args) = @_;
- my $disk = $args{'disk'};
- my $slice = $args{'slice'};
- my $device = $args{'device'} || ( $slice ? $slice->{'device'} : undef );
- return undef unless $device;
-
- my $label;
-
- # Prefer GPT labels when applicable
- my $base = $disk ? $disk->{'device'} : base_disk_device($device);
- if ($base) {
- $base =~ s{^/dev/}{};
- my $ds = $args{'disk_structure'} || get_disk_structure($base);
- if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
- my $idx = $slice ? slice_number($slice) : undef;
- if ( $idx && $ds->{'partitions'} && $ds->{'partitions'}->{$idx} ) {
- my $pl = $ds->{'partitions'}->{$idx}->{'label'};
- $label = $pl if ( $pl && $pl ne '(null)' );
- }
- if ( !$label && $idx && $ds->{'entries'} ) {
- foreach my $e ( @{ $ds->{'entries'} } ) {
- next unless ( $e->{'type'} && $e->{'type'} eq 'partition' );
- next
- unless ( defined $e->{'index'} && $e->{'index'} == $idx );
- if ( $e->{'label'} && $e->{'label'} ne '(null)' ) {
- $label = $e->{'label'};
- last;
- }
- }
- }
- }
- }
-
- # Fallback: GEOM label (glabel)
- if ( !$label && has_command('glabel') ) {
- my $glabel_out = backquote_command("glabel status 2>/dev/null");
- foreach my $line ( split( /\n/, $glabel_out ) ) {
- if ( $line =~ /^\s*(\S+)\s+\S+\s+(\S+)/ ) {
- my ( $lab, $prov ) = ( $1, $2 );
- $prov = "/dev/$prov" if ( $prov !~ m{^/dev/} );
- if ( $prov eq $device ) {
- $label = $lab;
- last;
- }
- }
- }
- }
- return $label;
-}
-
-sub preferred_device_path {
- my ($device) = @_;
- return $device unless $device;
-
- # Check for GPT label first
- if ( -e "/dev/gpt" ) {
- my $gpt_label =
- backquote_command( "gpart list 2>/dev/null | grep -A 10 "
- . quote_path($device)
- . " | grep 'label:' | head -1" );
- if ( $gpt_label =~ /label:\s*(\S+)/ && $1 ne '(null)' ) {
- my $label_path = "/dev/gpt/$1";
- return $label_path if ( -e $label_path );
- }
- }
-
- # Check for glabel label
- if ( has_command('glabel') ) {
- my $glabel_out = backquote_command("glabel status 2>/dev/null");
- foreach my $line ( split( /\n/, $glabel_out ) ) {
- if ( $line =~ /^(\S+)\s+\S+\s+(.+)$/ ) {
- my ( $label, $provider ) = ( $1, $2 );
- $provider =~ s/^\s+|\s+$//g;
- if ( $provider eq $device || "/dev/$provider" eq $device ) {
- my $label_path = "/dev/label/$label";
- return $label_path if ( -e $label_path );
- }
- }
- }
- }
-
- # Check for UFS label
- my $ufs_label =
- backquote_command( "tunefs -p "
- . quote_path($device)
- . " 2>/dev/null | grep 'volume label'" );
- if ( $ufs_label =~ /volume label.*\[([^\]]+)\]/ && $1 ne '' ) {
- my $label_path = "/dev/ufs/$1";
- return $label_path if ( -e $label_path );
- }
-
- # Default: return original device
- return $device;
-}
-
-sub detect_filesystem_type {
- my ( $device, $hint ) = @_;
- my $t;
- if ( has_command('fstyp') ) {
- $t =
- backquote_command( "fstyp " . quote_path($device) . " 2>/dev/null" );
- $t =~ s/[\r\n]+$//;
- }
- $t ||= $hint || '';
- $t = lc($t);
-
- # Normalize common variants
- if ( $t =~ /ufs/ ) { return 'ufs'; }
- if ( $t =~ /msdos|fat/ ) { return 'msdosfs'; }
- if ( $t =~ /ext[234]|ext2fs/ ) { return 'ext2fs'; }
- if ( $t =~ /zfs/ ) { return 'zfs'; }
- if ( $t =~ /swap/ ) { return 'swap'; }
- if ( $t =~ /^(ufs|ffs)$/ ) { return 'ufs'; }
- if ( $t =~ /^(msdos|msdosfs|fat|fat32)$/ ) { return 'msdosfs'; }
- if ( $t =~ /^(ext2|ext2fs)$/ ) { return 'ext2fs'; }
- if ( $t =~ /^zfs$/ ) { return 'zfs'; }
- if ( $t =~ /^swap/ ) { return 'swap'; }
- return $t || undef;
-}
-
-# Apply recommended ACL inherit flags for NFSv4 datasets (best-effort)
-sub acl_inherit_flags_cmd {
- my ($dataset) = @_;
- return undef if ( !$dataset );
- my $ds = quote_path($dataset);
- my $cmd =
- 'acltype=$(zfs get -H -o value acltype '
- . $ds
- . ' 2>/dev/null); '
- . 'mp=$(zfs get -H -o value mountpoint '
- . $ds
- . ' 2>/dev/null); '
- . 'if [ "$acltype" = "nfsv4" ] && [ -n "$mp" ] && '
- . '[ "$mp" != "-" ] && [ "$mp" != "none" ]; then ';
- if ( $^O eq 'freebsd' ) {
- $cmd .=
+ }
+ return undef;
+}
+
+# Get the current label for a slice/partition device (GPT label or glabel)
+sub get_device_label_name {
+ my (%args) = @_;
+ my $disk = $args{'disk'};
+ my $slice = $args{'slice'};
+ my $device = $args{'device'} || ( $slice ? $slice->{'device'} : undef );
+ return undef unless $device;
+
+ my $label;
+
+ # Prefer GPT labels when applicable
+ my $base = $disk ? $disk->{'device'} : base_disk_device($device);
+ if ($base) {
+ $base =~ s{^/dev/}{};
+ my $ds = $args{'disk_structure'} || get_disk_structure($base);
+ if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
+ my $idx = $slice ? slice_number($slice) : undef;
+ if ( $idx && $ds->{'partitions'} && $ds->{'partitions'}->{$idx} ) {
+ my $pl = $ds->{'partitions'}->{$idx}->{'label'};
+ $label = $pl if ( $pl && $pl ne '(null)' );
+ }
+ if ( !$label && $idx && $ds->{'entries'} ) {
+ foreach my $e ( @{ $ds->{'entries'} } ) {
+ next unless ( $e->{'type'} && $e->{'type'} eq 'partition' );
+ next
+ unless ( defined $e->{'index'} && $e->{'index'} == $idx );
+ if ( $e->{'label'} && $e->{'label'} ne '(null)' ) {
+ $label = $e->{'label'};
+ last;
+ }
+ }
+ }
+ }
+ }
+
+ # Fallback: GEOM label (glabel)
+ if ( !$label && has_command('glabel') ) {
+ my $glabel_out = backquote_command("glabel status 2>/dev/null");
+ foreach my $line ( split( /\n/, $glabel_out ) ) {
+ if ( $line =~ /^\s*(\S+)\s+\S+\s+(\S+)/ ) {
+ my ( $lab, $prov ) = ( $1, $2 );
+ $prov = "/dev/$prov" if ( $prov !~ m{^/dev/} );
+ if ( $prov eq $device ) {
+ $label = $lab;
+ last;
+ }
+ }
+ }
+ }
+ return $label;
+}
+
+sub preferred_device_path {
+ my ($device) = @_;
+ return $device unless $device;
+
+ # Check for GPT label first
+ if ( -e "/dev/gpt" ) {
+ my $gpt_label =
+ backquote_command( "gpart list 2>/dev/null | grep -A 10 "
+ . quote_path($device)
+ . " | grep 'label:' | head -1" );
+ if ( $gpt_label =~ /label:\s*(\S+)/ && $1 ne '(null)' ) {
+ my $label_path = "/dev/gpt/$1";
+ return $label_path if ( -e $label_path );
+ }
+ }
+
+ # Check for glabel label
+ if ( has_command('glabel') ) {
+ my $glabel_out = backquote_command("glabel status 2>/dev/null");
+ foreach my $line ( split( /\n/, $glabel_out ) ) {
+ if ( $line =~ /^(\S+)\s+\S+\s+(.+)$/ ) {
+ my ( $label, $provider ) = ( $1, $2 );
+ $provider =~ s/^\s+|\s+$//g;
+ if ( $provider eq $device || "/dev/$provider" eq $device ) {
+ my $label_path = "/dev/label/$label";
+ return $label_path if ( -e $label_path );
+ }
+ }
+ }
+ }
+
+ # Check for UFS label
+ my $ufs_label =
+ backquote_command( "tunefs -p "
+ . quote_path($device)
+ . " 2>/dev/null | grep 'volume label'" );
+ if ( $ufs_label =~ /volume label.*\[([^\]]+)\]/ && $1 ne '' ) {
+ my $label_path = "/dev/ufs/$1";
+ return $label_path if ( -e $label_path );
+ }
+
+ # Default: return original device
+ return $device;
+}
+
+sub detect_filesystem_type {
+ my ( $device, $hint ) = @_;
+ my $t;
+ if ( has_command('fstyp') ) {
+ $t =
+ backquote_command( "fstyp " . quote_path($device) . " 2>/dev/null" );
+ $t =~ s/[\r\n]+$//;
+ }
+ $t ||= $hint || '';
+ $t = lc($t);
+ $t =~ s/^\s+|\s+$//g;
+
+ # Prefer exact matches first
+ return 'ufs' if ( $t =~ /^(ufs|ffs)$/ );
+ return 'msdosfs' if ( $t =~ /^(msdos|msdosfs|fat|fat32)$/ );
+ return 'ext2fs' if ( $t =~ /^(ext2|ext2fs|ext3|ext4)$/ );
+ return 'zfs' if ( $t eq 'zfs' );
+ return 'swap' if ( $t eq 'swap' );
+
+ # Then allow bounded substring matches to avoid false positives
+ return 'ufs' if ( $t =~ /\b(ufs|ffs)\b/ );
+ return 'msdosfs' if ( $t =~ /\b(msdos|msdosfs|fat|fat32|vfat|exfat)\b/ );
+ return 'ext2fs' if ( $t =~ /\b(ext2|ext3|ext4|ext2fs)\b/ );
+ return 'zfs' if ( $t =~ /\bzfs\b/ );
+ return 'swap' if ( $t =~ /\bswap\b/ );
+
+ return $t || undef;
+}
+
+# Apply recommended ACL inherit flags for NFSv4 datasets (best-effort)
+sub acl_inherit_flags_cmd {
+ my ($dataset) = @_;
+ return undef if ( !$dataset );
+ my $ds = quote_path($dataset);
+ my $cmd =
+ 'acltype=$(zfs get -H -o value acltype '
+ . $ds
+ . ' 2>/dev/null); '
+ . 'mp=$(zfs get -H -o value mountpoint '
+ . $ds
+ . ' 2>/dev/null); '
+ . 'if [ "$acltype" = "nfsv4" ] && [ -n "$mp" ] && '
+ . '[ "$mp" != "-" ] && [ "$mp" != "none" ]; then ';
+ if ( $^O eq 'freebsd' ) {
+ $cmd .=
'if command -v getfacl >/dev/null 2>&1 && '
. 'command -v setfacl >/dev/null 2>&1; then '
. 'po=$(getfacl "$mp" 2>/dev/null | awk -F: '
@@ -1281,15 +1285,15 @@ sub acl_inherit_flags_cmd {
. '"/^[[:space:]]*group@/{print \\$2; exit}"); '
. 'pe=$(getfacl "$mp" 2>/dev/null | awk -F: '
. '"/^[[:space:]]*everyone@/{print \\$2; exit}"); '
- . 'if [ -n "$po" ] && [ -n "$pg" ] && [ -n "$pe" ]; then '
+ . 'if [ -n "$po" ] && [ -n "$pg" ] && [ -n "$pe" ]; then '
. 'setfacl -m "owner@:$po:fd-----:allow" '
. '-m "group@:$pg:fd-----:allow" '
. '-m "everyone@:$pe:fd-----:allow" "$mp"; '
- . 'fi; '
- . 'fi; ';
- }
- else {
- $cmd .=
+ . 'fi; '
+ . 'fi; ';
+ }
+ else {
+ $cmd .=
'if command -v nfs4_getfacl >/dev/null 2>&1 && '
. 'command -v nfs4_setfacl >/dev/null 2>&1; then '
. 'po=$(nfs4_getfacl "$mp" 2>/dev/null | awk -F: '
@@ -1298,915 +1302,915 @@ sub acl_inherit_flags_cmd {
. '"/GROUP@/{print \\$4; exit}"); '
. 'pe=$(nfs4_getfacl "$mp" 2>/dev/null | awk -F: '
. '"/EVERYONE@/{print \\$4; exit}"); '
- . 'if [ -n "$po" ] && [ -n "$pg" ] && [ -n "$pe" ]; then '
+ . 'if [ -n "$po" ] && [ -n "$pg" ] && [ -n "$pe" ]; then '
. 'nfs4_setfacl -a "A::OWNER@:$po:fd-----:allow" '
. '-a "A::GROUP@:$pg:fd-----:allow" '
. '-a "A::EVERYONE@:$pe:fd-----:allow" "$mp"; '
- . 'fi; '
- . 'fi; ';
- }
- $cmd .= "fi";
- return $cmd;
-}
-
-sub get_zfs_device_info {
- my ($object) = @_;
- return undef unless ( $object && $object->{'device'} );
- my ( $pools, $zfs_devices ) = build_zfs_devices_cache();
- my $device = $object->{'device'};
- my @ids = ($device);
- ( my $short = $device ) =~ s{^/dev/}{};
- push( @ids, $short, lc($device), lc($short) );
-
- # If this is a top-level partition, include GPT label aliases if present
- if ( $device =~ m{^/dev/([a-z]+[0-9]+)([ps])(\d+)$}i ) {
- my ( $base, $sep, $num ) = ( $1, $2, $3 );
- my $ds = get_disk_structure($base);
- my $part_label;
- if ( $ds && $ds->{'partitions'} && $ds->{'partitions'}->{$num} ) {
- my $pl = $ds->{'partitions'}->{$num}->{'label'};
- $part_label = $pl if ( $pl && $pl ne '(null)' );
- }
- if ( !$part_label && $ds && $ds->{'entries'} ) {
- foreach my $e ( @{ $ds->{'entries'} } ) {
- next unless ( $e->{'type'} && $e->{'type'} eq 'partition' );
- next unless ( defined $e->{'index'} && $e->{'index'} == $num );
- if ( $e->{'label'} && $e->{'label'} ne '(null)' ) {
- $part_label = $e->{'label'};
- last;
- }
- }
- }
- my $part_name = $base . $sep . $num;
- my $scheme = ( $sep eq 'p' ) ? 'GPT' : '';
- push(
- @ids,
- _possible_partition_ids(
- $base, $scheme, $num, $part_name, $part_label
- )
- );
- }
- return _find_in_zfs( $zfs_devices, @ids );
-}
-
-sub is_zfs_device {
- my ($object) = @_;
- return 0 unless ( $object && $object->{'device'} );
-
- # Trust explicit type hints first
- if ( defined $object->{'type'} && $object->{'type'} =~ /zfs/i ) {
- return 1;
- }
- return get_zfs_device_info($object) ? 1 : 0;
-}
-
-sub get_check_filesystem_command {
- my ( $disk, $slice, $part ) = @_;
- my $device = $part ? $part->{'device'} : $slice->{'device'};
- my $hint = $part ? $part->{'type'} : $slice->{'type'};
- my $fstype = detect_filesystem_type( $device, $hint );
- my $qdev = quote_path($device);
-
- # Map to specific fsck tools when available; else use fsck -t
- if ( $fstype && $fstype eq 'ufs' ) {
- return has_command('fsck_ufs')
- ? "fsck_ufs -y $qdev"
- : "fsck -t ufs -y $qdev";
- }
- if ( $fstype && $fstype eq 'msdosfs' ) {
- return has_command('fsck_msdosfs')
- ? "fsck_msdosfs -y $qdev"
- : "fsck -t msdosfs -y $qdev";
- }
- if ( $fstype && $fstype eq 'ext2fs' ) {
- return has_command('fsck_ext2fs')
- ? "fsck_ext2fs -y $qdev"
- : "fsck -t ext2fs -y $qdev";
- }
- if ( $fstype && $fstype eq 'zfs' ) {
- return
- "zpool status 2>&1"
- ; # caller should avoid fsck for ZFS, but safe fallback
- }
- if ( $fstype && $fstype eq 'swap' ) {
- return "echo 'swap device - fsck not applicable'";
- }
-
- # Generic fallback
- return "fsck -y $qdev";
-}
-
-sub show_filesystem_buttons {
- my ( $hiddens, $st, $object, $return_url ) = @_;
- if ( $return_url && $return_url !~ m{^/} ) {
- $return_url = "/$module_name/$return_url";
- }
-
- # Use preferred device path (label-based if available)
- my $preferred_dev = preferred_device_path( $object->{'device'} );
- print ui_buttons_row( "newfs_form.cgi", $text{'part_newfs'},
- $text{'part_newfsdesc'}, $hiddens );
-
- # Offer fsck only when a filesystem is detectable and supported
- my $is_swap = ( @$st && $st->[1] eq 'swap' )
- || ( $object->{'type'} && $object->{'type'} =~ /freebsd-swap|^82$/i );
- my $is_zfs = is_zfs_device($object);
- my $fsck_type;
- if ( has_command('fstyp') ) {
- $fsck_type = detect_filesystem_type( $object->{'device'}, undef );
- if ( !$fsck_type ) {
- $fsck_type =
- detect_filesystem_type( $object->{'device'}, $object->{'type'} );
- }
- }
- else {
- $fsck_type =
- detect_filesystem_type( $object->{'device'}, $object->{'type'} );
- }
- if ( $fsck_type
- && $fsck_type ne 'swap'
- && $fsck_type ne 'zfs'
- && !$is_swap
- && !$is_zfs )
- {
- print ui_buttons_row( "fsck.cgi", $text{'part_fsck'},
- $text{'part_fsckdesc'}, $hiddens );
- }
- if ( !@$st ) {
- if ( $object->{'type'} eq 'swap'
- or $object->{'type'} eq '82'
- or $object->{'type'} eq 'freebsd-swap' )
- {
- my $mount_h = ui_hidden( "newdev", $preferred_dev )
- . ui_hidden( "type", "swap" );
- $mount_h .= ui_hidden( "return", $return_url ) if ($return_url);
- print ui_buttons_row( "../mount/edit_mount.cgi",
- $text{'part_newmount2'}, $text{'part_mountmsg2'}, $mount_h );
- }
- else {
- my $fstype =
- detect_filesystem_type( $object->{'device'}, $object->{'type'} );
- my $can_mount =
- ( $fstype && $fstype ne 'zfs' && $fstype ne 'swap' ) ? 1 : 0;
- if ($can_mount) {
- my $mount_h = ui_hidden( "newdev", $preferred_dev )
- . ui_hidden( "type", $fstype );
- $mount_h .= ui_hidden( "return", $return_url ) if ($return_url);
- print ui_buttons_row( "../mount/edit_mount.cgi",
- $text{'part_newmount'}, $text{'part_mountmsg'}, $mount_h );
- }
- }
- }
-}
-
-#---------------------------------------------------------------------
-# ZFS and GEOM related functions are largely unchanged.
-sub get_all_zfs_info {
-
- # Wrapper built from the structured ZFS devices cache
- my ( $pools, $devices ) = build_zfs_devices_cache();
- my %zfs_info;
- foreach my $id ( keys %$devices ) {
- next unless $id =~ /^\/dev\//; # focus on canonical /dev/* keys
- my $dev = $devices->{$id};
- my $suffix =
- ( $dev->{'vdev_type'} && $dev->{'vdev_type'} eq 'log' )
- ? '(log)'
- : '(data)';
- $zfs_info{$id} = $dev->{'pool'} . ' ' . $suffix;
- }
- return \%zfs_info;
-}
-
-sub get_type_description {
- my ($type) = @_;
- my %type_map = (
- 'freebsd' => 'FreeBSD',
- 'freebsd-ufs' => 'FreeBSD UFS',
- 'freebsd-swap' => 'FreeBSD Swap',
- 'freebsd-vinum' => 'FreeBSD Vinum',
- 'freebsd-zfs' => 'FreeBSD ZFS',
- 'freebsd-boot' => 'FreeBSD Boot',
- 'efi' => 'EFI System',
- 'bios-boot' => 'BIOS Boot',
- 'ms-basic-data' => 'Microsoft Basic Data (FAT/NTFS/exFAT)',
- 'ms-reserved' => 'Microsoft Reserved',
- 'ms-recovery' => 'Microsoft Recovery',
- 'apple-ufs' => 'Apple UFS',
- 'apple-hfs' => 'Apple HFS',
- 'apple-boot' => 'Apple Boot',
- 'apple-raid' => 'Apple RAID',
- 'apple-label' => 'Apple Label',
- 'linux-data' => 'Linux Data (ext2/3/4, xfs, btrfs, etc.)',
- 'linux-swap' => 'Linux Swap',
- 'linux-lvm' => 'Linux LVM',
- 'linux-raid' => 'Linux RAID',
- );
- return $type_map{$type} || $type;
-}
-
-sub get_disk_structure {
- my ($device) = @_;
- my $result = { 'entries' => [], 'partitions' => {} };
- my $cmd = "gpart show -l " . quote_path($device) . " 2>&1";
- my $out = backquote_command($cmd);
- if ( $out =~ /=>\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+\(([^)]+)\)/ ) {
- my $start_block = $1; # starting block
- my $size_blocks = $2; # number of blocks
- $result->{'total_blocks'} =
- $start_block + $size_blocks; # last addressable block + 1
- $result->{'device_name'} = $3; # device name (e.g., da0)
- $result->{'scheme'} = $4; # GPT/MBR
- $result->{'size_human'} = $5; # human size from header
- }
- foreach my $line ( split( /\n/, $out ) ) {
-
- # Free space rows
- if ( $line =~ /^\s+(\d+)\s+(\d+)\s+-\s+free\s+-\s+\(([^)]+)\)/ ) {
- push @{ $result->{'entries'} },
- {
- 'start' => $1,
- 'size' => $2,
- 'size_human' => $3,
- 'type' => 'free'
- };
- next;
- }
-
+ . 'fi; '
+ . 'fi; ';
+ }
+ $cmd .= "fi";
+ return $cmd;
+}
+
+sub get_zfs_device_info {
+ my ($object) = @_;
+ return undef unless ( $object && $object->{'device'} );
+ my ( $pools, $zfs_devices ) = build_zfs_devices_cache();
+ my $device = $object->{'device'};
+ my @ids = ($device);
+ ( my $short = $device ) =~ s{^/dev/}{};
+ push( @ids, $short, lc($device), lc($short) );
+
+ # If this is a top-level partition, include GPT label aliases if present
+ if ( $device =~ m{^/dev/([a-z]+[0-9]+)([ps])(\d+)$}i ) {
+ my ( $base, $sep, $num ) = ( $1, $2, $3 );
+ my $ds = get_disk_structure($base);
+ my $part_label;
+ if ( $ds && $ds->{'partitions'} && $ds->{'partitions'}->{$num} ) {
+ my $pl = $ds->{'partitions'}->{$num}->{'label'};
+ $part_label = $pl if ( $pl && $pl ne '(null)' );
+ }
+ if ( !$part_label && $ds && $ds->{'entries'} ) {
+ foreach my $e ( @{ $ds->{'entries'} } ) {
+ next unless ( $e->{'type'} && $e->{'type'} eq 'partition' );
+ next unless ( defined $e->{'index'} && $e->{'index'} == $num );
+ if ( $e->{'label'} && $e->{'label'} ne '(null)' ) {
+ $part_label = $e->{'label'};
+ last;
+ }
+ }
+ }
+ my $part_name = $base . $sep . $num;
+ my $scheme = ( $sep eq 'p' ) ? 'GPT' : '';
+ push(
+ @ids,
+ _possible_partition_ids(
+ $base, $scheme, $num, $part_name, $part_label
+ )
+ );
+ }
+ return _find_in_zfs( $zfs_devices, @ids );
+}
+
+sub is_zfs_device {
+ my ($object) = @_;
+ return 0 unless ( $object && $object->{'device'} );
+
+ # Trust explicit type hints first
+ if ( defined $object->{'type'} && $object->{'type'} =~ /zfs/i ) {
+ return 1;
+ }
+ return get_zfs_device_info($object) ? 1 : 0;
+}
+
+sub get_check_filesystem_command {
+ my ( $disk, $slice, $part ) = @_;
+ my $device = $part ? $part->{'device'} : $slice->{'device'};
+ my $hint = $part ? $part->{'type'} : $slice->{'type'};
+ my $fstype = detect_filesystem_type( $device, $hint );
+ my $qdev = quote_path($device);
+
+ # Map to specific fsck tools when available; else use fsck -t
+ if ( $fstype && $fstype eq 'ufs' ) {
+ return has_command('fsck_ufs')
+ ? "fsck_ufs -y $qdev"
+ : "fsck -t ufs -y $qdev";
+ }
+ if ( $fstype && $fstype eq 'msdosfs' ) {
+ return has_command('fsck_msdosfs')
+ ? "fsck_msdosfs -y $qdev"
+ : "fsck -t msdosfs -y $qdev";
+ }
+ if ( $fstype && $fstype eq 'ext2fs' ) {
+ return has_command('fsck_ext2fs')
+ ? "fsck_ext2fs -y $qdev"
+ : "fsck -t ext2fs -y $qdev";
+ }
+ if ( $fstype && $fstype eq 'zfs' ) {
+ return
+ "zpool status 2>&1"
+ ; # caller should avoid fsck for ZFS, but safe fallback
+ }
+ if ( $fstype && $fstype eq 'swap' ) {
+ return "echo 'swap device - fsck not applicable'";
+ }
+
+ # Generic fallback
+ return "fsck -y $qdev";
+}
+
+sub show_filesystem_buttons {
+ my ( $hiddens, $st, $object, $return_url ) = @_;
+ if ( $return_url && $return_url !~ m{^/} ) {
+ $return_url = "/$module_name/$return_url";
+ }
+
+ # Use preferred device path (label-based if available)
+ my $preferred_dev = preferred_device_path( $object->{'device'} );
+ print ui_buttons_row( "newfs_form.cgi", $text{'part_newfs'},
+ $text{'part_newfsdesc'}, $hiddens );
+
+ # Offer fsck only when a filesystem is detectable and supported
+ my $is_swap = ( @$st && $st->[1] eq 'swap' )
+ || ( $object->{'type'} && $object->{'type'} =~ /freebsd-swap|^82$/i );
+ my $is_zfs = is_zfs_device($object);
+ my $fsck_type;
+ if ( has_command('fstyp') ) {
+ $fsck_type = detect_filesystem_type( $object->{'device'}, undef );
+ if ( !$fsck_type ) {
+ $fsck_type =
+ detect_filesystem_type( $object->{'device'}, $object->{'type'} );
+ }
+ }
+ else {
+ $fsck_type =
+ detect_filesystem_type( $object->{'device'}, $object->{'type'} );
+ }
+ if ( $fsck_type
+ && $fsck_type ne 'swap'
+ && $fsck_type ne 'zfs'
+ && !$is_swap
+ && !$is_zfs )
+ {
+ print ui_buttons_row( "fsck.cgi", $text{'part_fsck'},
+ $text{'part_fsckdesc'}, $hiddens );
+ }
+ if ( !@$st ) {
+ if ( $object->{'type'} eq 'swap'
+ or $object->{'type'} eq '82'
+ or $object->{'type'} eq 'freebsd-swap' )
+ {
+ my $mount_h = ui_hidden( "newdev", $preferred_dev )
+ . ui_hidden( "type", "swap" );
+ $mount_h .= ui_hidden( "return", $return_url ) if ($return_url);
+ print ui_buttons_row( "../mount/edit_mount.cgi",
+ $text{'part_newmount2'}, $text{'part_mountmsg2'}, $mount_h );
+ }
+ else {
+ my $fstype =
+ detect_filesystem_type( $object->{'device'}, $object->{'type'} );
+ my $can_mount =
+ ( $fstype && $fstype ne 'zfs' && $fstype ne 'swap' ) ? 1 : 0;
+ if ($can_mount) {
+ my $mount_h = ui_hidden( "newdev", $preferred_dev )
+ . ui_hidden( "type", $fstype );
+ $mount_h .= ui_hidden( "return", $return_url ) if ($return_url);
+ print ui_buttons_row( "../mount/edit_mount.cgi",
+ $text{'part_newmount'}, $text{'part_mountmsg'}, $mount_h );
+ }
+ }
+ }
+}
+
+#---------------------------------------------------------------------
+# ZFS and GEOM related functions are largely unchanged.
+sub get_all_zfs_info {
+
+ # Wrapper built from the structured ZFS devices cache
+ my ( $pools, $devices ) = build_zfs_devices_cache();
+ my %zfs_info;
+ foreach my $id ( keys %$devices ) {
+ next unless $id =~ /^\/dev\//; # focus on canonical /dev/* keys
+ my $dev = $devices->{$id};
+ my $suffix =
+ ( $dev->{'vdev_type'} && $dev->{'vdev_type'} eq 'log' )
+ ? '(log)'
+ : '(data)';
+ $zfs_info{$id} = $dev->{'pool'} . ' ' . $suffix;
+ }
+ return \%zfs_info;
+}
+
+sub get_type_description {
+ my ($type) = @_;
+ my %type_map = (
+ 'freebsd' => 'FreeBSD',
+ 'freebsd-ufs' => 'FreeBSD UFS',
+ 'freebsd-swap' => 'FreeBSD Swap',
+ 'freebsd-vinum' => 'FreeBSD Vinum',
+ 'freebsd-zfs' => 'FreeBSD ZFS',
+ 'freebsd-boot' => 'FreeBSD Boot',
+ 'efi' => 'EFI System',
+ 'bios-boot' => 'BIOS Boot',
+ 'ms-basic-data' => 'Microsoft Basic Data (FAT/NTFS/exFAT)',
+ 'ms-reserved' => 'Microsoft Reserved',
+ 'ms-recovery' => 'Microsoft Recovery',
+ 'apple-ufs' => 'Apple UFS',
+ 'apple-hfs' => 'Apple HFS',
+ 'apple-boot' => 'Apple Boot',
+ 'apple-raid' => 'Apple RAID',
+ 'apple-label' => 'Apple Label',
+ 'linux-data' => 'Linux Data (ext2/3/4, xfs, btrfs, etc.)',
+ 'linux-swap' => 'Linux Swap',
+ 'linux-lvm' => 'Linux LVM',
+ 'linux-raid' => 'Linux RAID',
+ );
+ return $type_map{$type} || $type;
+}
+
+sub get_disk_structure {
+ my ($device) = @_;
+ my $result = { 'entries' => [], 'partitions' => {} };
+ my $cmd = "gpart show -l " . quote_path($device) . " 2>&1";
+ my $out = backquote_command($cmd);
+ if ( $out =~ /=>\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+\(([^)]+)\)/ ) {
+ my $start_block = $1; # starting block
+ my $size_blocks = $2; # number of blocks
+ $result->{'total_blocks'} =
+ $start_block + $size_blocks; # last addressable block + 1
+ $result->{'device_name'} = $3; # device name (e.g., da0)
+ $result->{'scheme'} = $4; # GPT/MBR
+ $result->{'size_human'} = $5; # human size from header
+ }
+ foreach my $line ( split( /\n/, $out ) ) {
+
+ # Free space rows
+ if ( $line =~ /^\s+(\d+)\s+(\d+)\s+-\s+free\s+-\s+\(([^)]+)\)/ ) {
+ push @{ $result->{'entries'} },
+ {
+ 'start' => $1,
+ 'size' => $2,
+ 'size_human' => $3,
+ 'type' => 'free'
+ };
+ next;
+ }
+
# Partition rows from `gpart show -l` have: start size index label
# [flags] (size_human)
# Some systems include optional tokens like "[active]" after the label.
# Accept them.
- if ( $line =~
- /^\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)(?:\s+\[[^\]]+\])?\s+\(([^)]+)\)/
- )
- {
- push @{ $result->{'entries'} },
- {
- 'start' => $1,
- 'size' => $2,
- 'index' => $3,
- 'label' => $4,
- 'size_human' => $5,
- 'type' => 'partition'
- };
- }
- }
-
- # Merge additional info from 'gpart list' directly, keyed by Name -> index
- my $list_out =
- backquote_command( "gpart list " . quote_path($device) . " 2>&1" );
- my ( %parts, $current_idx );
- foreach my $line ( split( /\n/, $list_out ) ) {
- if ( $line =~ /^\s*(?:\d+\.\s*)?Name:\s*(\S+)/i ) {
- my $name = $1; # e.g., da0p2 or da0s2
- if ( $name =~ /[ps](\d+)$/ ) {
- $current_idx = int($1);
- $parts{$current_idx} ||= { name => $name };
- }
- else {
- undef $current_idx; # not a partition provider line
- }
- }
- elsif ( defined $current_idx && $line =~ /^\s*Index:\s*(\d+)/i ) {
-
- # Optional cross-check; ignore value and trust Name-derived index
- next;
- }
- elsif ( defined $current_idx && $line =~ /^\s*label:\s*(\S+)/i ) {
- $parts{$current_idx}->{'label'} = $1;
- }
- elsif ( defined $current_idx && $line =~ /^\s*type:\s*(\S+)/i ) {
- $parts{$current_idx}->{'type'} = $1;
- }
- elsif ( defined $current_idx && $line =~ /^\s*rawtype:\s*(\S+)/i ) {
- $parts{$current_idx}->{'rawtype'} = $1;
- }
- elsif ( defined $current_idx && $line =~ /^\s*length:\s*(\d+)/i ) {
- $parts{$current_idx}->{'length'} = $1;
- }
- elsif ( defined $current_idx && $line =~ /^\s*offset:\s*(\d+)/i ) {
- $parts{$current_idx}->{'offset'} = $1;
- }
- elsif ( $line =~ /Sectorsize:\s*(\d+)/i ) {
- $result->{'sectorsize'} = int($1);
- }
- elsif ( $line =~ /Mediasize:\s*(\d+)/i ) {
- $result->{'mediasize'} = int($1);
- }
- }
- $result->{'partitions'} = \%parts;
- foreach my $entry ( @{ $result->{'entries'} } ) {
- next unless ( $entry->{'type'} eq 'partition' && $entry->{'index'} );
- my $idx = $entry->{'index'};
- if ( $parts{$idx} ) {
-
- # Prefer label from gpart list if present and meaningful
- if ( $parts{$idx}->{'label'}
- && $parts{$idx}->{'label'} ne '(null)' )
- {
- $entry->{'label'} = $parts{$idx}->{'label'};
- }
-
- # Attach resolved type (rawtype/type) for downstream consumers
- $entry->{'part_type'} =
- $parts{$idx}->{'type'}
- || $parts{$idx}->{'rawtype'}
- || $entry->{'part_type'};
-
- # Also store rawtype for downstream consumers
- $entry->{'rawtype'} = $parts{$idx}->{'rawtype'}
- if ( $parts{$idx}->{'rawtype'} );
- }
- }
- return $result;
-}
-
-sub get_disk_sectorsize {
- my ($device) = @_;
-
- # Normalize device for diskinfo (expects provider name like da0)
- my $dev = $device;
- $dev =~ s{^/dev/}{};
-
- # Prefer verbose output which explicitly lists sectorsize
- my $outv =
- backquote_command( "diskinfo -v " . quote_path($dev) . " 2>/dev/null" );
- if ( $outv =~ /sectorsize:\s*(\d+)/i ) {
- return int($1);
- }
-
- # Fallback to non-verbose; actual format: name sectorsize mediasize ...
- my $out =
- backquote_command( "diskinfo " . quote_path($dev) . " 2>/dev/null" );
- if ( $out =~ /^\S+\s+(\d+)\s+\d+/ ) {
- return int($1); # second field is sectorsize
- }
-
- # Last resort: ask gpart list for sectorsize
- my $base = $dev;
- my $ds = get_disk_structure($base);
- if ( $ds && $ds->{'sectorsize'} ) { return int( $ds->{'sectorsize'} ); }
- return undef;
-}
-
-# Derive the base disk device (e.g., /dev/da0 from /dev/da0p2)
-sub base_disk_device {
- my ($device) = @_;
- return undef unless $device;
- my $d = $device;
- $d =~ s{^/dev/}{};
- $d =~ s{(p|s)\d+.*$}{}; # strip partition/slice suffix
- return "/dev/$d";
-}
-
-# Compute bytes from a block count for a given device
-sub bytes_from_blocks {
- my ( $device, $blocks ) = @_;
- return undef unless defined $blocks;
- my $base = base_disk_device($device) || $device;
- my $ss = get_disk_sectorsize($base) || 512;
- return $blocks * $ss;
-}
-
-# Safe wrapper for nice_size that ensures bytes input
-# Accepts either raw bytes or (device, blocks) pair
-sub safe_nice_size {
- my ( $arg1, $arg2 ) = @_;
- my $bytes;
- if ( defined $arg2 ) {
-
- # Called as (device, blocks)
- $bytes = bytes_from_blocks( $arg1, $arg2 );
- }
- else {
- # Called as (bytes)
- $bytes = $arg1;
- }
- return '-' unless defined $bytes && $bytes >= 0;
- my $s = nice_size($bytes);
-
- # Normalize IEC suffixes to SI-style labels if present
- $s =~ s/\bKiB\b/KB/g;
- $s =~ s/\bMiB\b/MB/g;
- $s =~ s/\bGiB\b/GB/g;
- $s =~ s/\bTiB\b/TB/g;
- $s =~ s/\bPiB\b/PB/g;
- $s =~ s/\bEiB\b/EB/g;
- return $s;
-}
-
-sub build_zfs_devices_cache {
- my %pools;
- my %devices;
- my $cmd = "zpool status 2>&1";
- my $out = backquote_command($cmd);
- my (
- $current_pool, $in_config, $current_vdev_type,
- $current_vdev_group, $is_mirrored, $is_raidz,
- $raidz_level, $is_single, $is_striped,
- $vdev_count
- );
- $current_vdev_type = 'data';
- foreach my $line ( split( /\n/, $out ) ) {
- if ( $line =~ /^\s*pool:\s+(\S+)/ ) {
- $current_pool = $1;
- $pools{$current_pool} = 1;
- $in_config = 0;
- $current_vdev_type = 'data';
- }
- elsif ( $line =~ /^\s*config:/ ) {
- $in_config = 1;
- $current_vdev_group = undef;
- $is_mirrored = 0;
- $is_raidz = 0;
- $raidz_level = 0;
- $is_single = 0;
- $is_striped = 0;
- $vdev_count = 0;
- }
- elsif ( $in_config and $line =~ /^\s+logs/ ) {
- $current_vdev_type = 'log';
- $current_vdev_group = undef;
- }
- elsif ( $in_config and $line =~ /^\s+cache/ ) {
- $current_vdev_type = 'cache';
- $current_vdev_group = undef;
- }
- elsif ( $in_config and $line =~ /^\s+spares/ ) {
- $current_vdev_type = 'spare';
- $current_vdev_group = undef;
- }
- elsif ( $in_config and $line =~ /^\s+mirror-(\d+)/ ) {
- $current_vdev_group = "mirror-$1";
- $is_mirrored = 1;
- $is_raidz = 0;
- $is_single = 0;
- $is_striped = 0;
- $vdev_count = 0;
- }
- elsif ( $in_config and $line =~ /^\s+raidz(\d+)?-(\d+)/ ) {
- $current_vdev_group = "raidz" . ( $1 || "1" ) . "-$2";
- $is_mirrored = 0;
- $is_raidz = 1;
- $raidz_level = $1 || 1;
- $is_single = 0;
- $is_striped = 0;
- $vdev_count = 0;
- }
- elsif ( $in_config and $line =~ /^\s+(\S+)\s+(\S+)/ ) {
- my $device = $1;
- my $state = $2;
- next
- if ( $device eq $current_pool
- or $device =~ /^mirror-/
- or $device =~ /^raidz\d*-/ );
- if ($current_vdev_group) { $vdev_count++; }
- else { $is_single = 1; }
- my $device_id = $device;
- $device_id = $1 if ( $device =~ /^gpt\/(.*)/ );
- $devices{$device} = {
- 'pool' => $current_pool,
- 'vdev_type' => $current_vdev_type,
- 'is_mirrored' => $is_mirrored,
- 'is_raidz' => $is_raidz,
- 'raidz_level' => $raidz_level,
- 'is_single' => $is_single,
- 'is_striped' => $is_striped,
- 'vdev_group' => $current_vdev_group,
- 'vdev_count' => $vdev_count
- };
- $devices{"gpt/$device"} = $devices{$device}
- if ( $device !~ /^gpt\// );
- $devices{"/dev/$device"} = $devices{$device};
-
- if ( $device !~ /^gpt\// ) {
- $devices{"/dev/gpt/$device"} = $devices{$device};
- }
- $devices{ lc($device) } = $devices{$device};
- if ( $device !~ /^gpt\// ) {
- $devices{ "gpt/" . lc($device) } = $devices{$device};
- $devices{ "/dev/gpt/" . lc($device) } = $devices{$device};
- }
- }
- }
- return ( \%pools, \%devices );
-}
-
-sub get_format_type {
- my ($part) = @_;
- if ( $part->{'type'} =~ /^freebsd-/ ) {
- return get_type_description( $part->{'type'} );
- }
- return get_type_description( $part->{'type'} ) || $part->{'type'};
-}
-
-# Build possible ids for a partition given base device, scheme and metadata
-sub _label_conflicts_with_device {
- my ( $label, $base_device, $scheme, $part_num, $part_name ) = @_;
- return 0 unless defined $label && $label ne '-' && $label ne '(null)';
- my $sep = ( $scheme && $scheme eq 'GPT' ) ? 'p' : 's';
- my $expected =
- ( defined $base_device && defined $part_num && length($base_device) )
- ? "$base_device$sep$part_num"
- : undef;
- if ( defined $expected && lc($label) eq lc($expected) ) {
- return 0;
- }
- if ( defined $part_name
- && $part_name ne '-'
- && lc($label) eq lc($part_name) )
- {
- return 0;
- }
-
-# If a label looks like a real disk partition but doesn't match this partition,
+ if ( $line =~
+ /^\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)(?:\s+\[[^\]]+\])?\s+\(([^)]+)\)/
+ )
+ {
+ push @{ $result->{'entries'} },
+ {
+ 'start' => $1,
+ 'size' => $2,
+ 'index' => $3,
+ 'label' => $4,
+ 'size_human' => $5,
+ 'type' => 'partition'
+ };
+ }
+ }
+
+ # Merge additional info from 'gpart list' directly, keyed by Name -> index
+ my $list_out =
+ backquote_command( "gpart list " . quote_path($device) . " 2>&1" );
+ my ( %parts, $current_idx );
+ foreach my $line ( split( /\n/, $list_out ) ) {
+ if ( $line =~ /^\s*(?:\d+\.\s*)?Name:\s*(\S+)/i ) {
+ my $name = $1; # e.g., da0p2 or da0s2
+ if ( $name =~ /[ps](\d+)$/ ) {
+ $current_idx = int($1);
+ $parts{$current_idx} ||= { name => $name };
+ }
+ else {
+ undef $current_idx; # not a partition provider line
+ }
+ }
+ elsif ( defined $current_idx && $line =~ /^\s*Index:\s*(\d+)/i ) {
+
+ # Optional cross-check; ignore value and trust Name-derived index
+ next;
+ }
+ elsif ( defined $current_idx && $line =~ /^\s*label:\s*(\S+)/i ) {
+ $parts{$current_idx}->{'label'} = $1;
+ }
+ elsif ( defined $current_idx && $line =~ /^\s*type:\s*(\S+)/i ) {
+ $parts{$current_idx}->{'type'} = $1;
+ }
+ elsif ( defined $current_idx && $line =~ /^\s*rawtype:\s*(\S+)/i ) {
+ $parts{$current_idx}->{'rawtype'} = $1;
+ }
+ elsif ( defined $current_idx && $line =~ /^\s*length:\s*(\d+)/i ) {
+ $parts{$current_idx}->{'length'} = $1;
+ }
+ elsif ( defined $current_idx && $line =~ /^\s*offset:\s*(\d+)/i ) {
+ $parts{$current_idx}->{'offset'} = $1;
+ }
+ elsif ( $line =~ /Sectorsize:\s*(\d+)/i ) {
+ $result->{'sectorsize'} = int($1);
+ }
+ elsif ( $line =~ /Mediasize:\s*(\d+)/i ) {
+ $result->{'mediasize'} = int($1);
+ }
+ }
+ $result->{'partitions'} = \%parts;
+ foreach my $entry ( @{ $result->{'entries'} } ) {
+ next unless ( $entry->{'type'} eq 'partition' && $entry->{'index'} );
+ my $idx = $entry->{'index'};
+ if ( $parts{$idx} ) {
+
+ # Prefer label from gpart list if present and meaningful
+ if ( $parts{$idx}->{'label'}
+ && $parts{$idx}->{'label'} ne '(null)' )
+ {
+ $entry->{'label'} = $parts{$idx}->{'label'};
+ }
+
+ # Attach resolved type (rawtype/type) for downstream consumers
+ $entry->{'part_type'} =
+ $parts{$idx}->{'type'}
+ || $parts{$idx}->{'rawtype'}
+ || $entry->{'part_type'};
+
+ # Also store rawtype for downstream consumers
+ $entry->{'rawtype'} = $parts{$idx}->{'rawtype'}
+ if ( $parts{$idx}->{'rawtype'} );
+ }
+ }
+ return $result;
+}
+
+sub get_disk_sectorsize {
+ my ($device) = @_;
+
+ # Normalize device for diskinfo (expects provider name like da0)
+ my $dev = $device;
+ $dev =~ s{^/dev/}{};
+
+ # Prefer verbose output which explicitly lists sectorsize
+ my $outv =
+ backquote_command( "diskinfo -v " . quote_path($dev) . " 2>/dev/null" );
+ if ( $outv =~ /sectorsize:\s*(\d+)/i ) {
+ return int($1);
+ }
+
+ # Fallback to non-verbose; actual format: name sectorsize mediasize ...
+ my $out =
+ backquote_command( "diskinfo " . quote_path($dev) . " 2>/dev/null" );
+ if ( $out =~ /^\S+\s+(\d+)\s+\d+/ ) {
+ return int($1); # second field is sectorsize
+ }
+
+ # Last resort: ask gpart list for sectorsize
+ my $base = $dev;
+ my $ds = get_disk_structure($base);
+ if ( $ds && $ds->{'sectorsize'} ) { return int( $ds->{'sectorsize'} ); }
+ return undef;
+}
+
+# Derive the base disk device (e.g., /dev/da0 from /dev/da0p2)
+sub base_disk_device {
+ my ($device) = @_;
+ return undef unless $device;
+ my $d = $device;
+ $d =~ s{^/dev/}{};
+ $d =~ s{(p|s)\d+.*$}{}; # strip partition/slice suffix
+ return "/dev/$d";
+}
+
+# Compute bytes from a block count for a given device
+sub bytes_from_blocks {
+ my ( $device, $blocks ) = @_;
+ return undef unless defined $blocks;
+ my $base = base_disk_device($device) || $device;
+ my $ss = get_disk_sectorsize($base) || 512;
+ return $blocks * $ss;
+}
+
+# Safe wrapper for nice_size that ensures bytes input
+# Accepts either raw bytes or (device, blocks) pair
+sub safe_nice_size {
+ my ( $arg1, $arg2 ) = @_;
+ my $bytes;
+ if ( defined $arg2 ) {
+
+ # Called as (device, blocks)
+ $bytes = bytes_from_blocks( $arg1, $arg2 );
+ }
+ else {
+ # Called as (bytes)
+ $bytes = $arg1;
+ }
+ return '-' unless defined $bytes && $bytes >= 0;
+ my $s = nice_size($bytes);
+
+ # Normalize IEC suffixes to SI-style labels if present
+ $s =~ s/\bKiB\b/KB/g;
+ $s =~ s/\bMiB\b/MB/g;
+ $s =~ s/\bGiB\b/GB/g;
+ $s =~ s/\bTiB\b/TB/g;
+ $s =~ s/\bPiB\b/PB/g;
+ $s =~ s/\bEiB\b/EB/g;
+ return $s;
+}
+
+sub build_zfs_devices_cache {
+ my %pools;
+ my %devices;
+ my $cmd = "zpool status 2>&1";
+ my $out = backquote_command($cmd);
+ my (
+ $current_pool, $in_config, $current_vdev_type,
+ $current_vdev_group, $is_mirrored, $is_raidz,
+ $raidz_level, $is_single, $is_striped,
+ $vdev_count
+ );
+ $current_vdev_type = 'data';
+ foreach my $line ( split( /\n/, $out ) ) {
+ if ( $line =~ /^\s*pool:\s+(\S+)/ ) {
+ $current_pool = $1;
+ $pools{$current_pool} = 1;
+ $in_config = 0;
+ $current_vdev_type = 'data';
+ }
+ elsif ( $line =~ /^\s*config:/ ) {
+ $in_config = 1;
+ $current_vdev_group = undef;
+ $is_mirrored = 0;
+ $is_raidz = 0;
+ $raidz_level = 0;
+ $is_single = 0;
+ $is_striped = 0;
+ $vdev_count = 0;
+ }
+ elsif ( $in_config and $line =~ /^\s+logs/ ) {
+ $current_vdev_type = 'log';
+ $current_vdev_group = undef;
+ }
+ elsif ( $in_config and $line =~ /^\s+cache/ ) {
+ $current_vdev_type = 'cache';
+ $current_vdev_group = undef;
+ }
+ elsif ( $in_config and $line =~ /^\s+spares/ ) {
+ $current_vdev_type = 'spare';
+ $current_vdev_group = undef;
+ }
+ elsif ( $in_config and $line =~ /^\s+mirror-(\d+)/ ) {
+ $current_vdev_group = "mirror-$1";
+ $is_mirrored = 1;
+ $is_raidz = 0;
+ $is_single = 0;
+ $is_striped = 0;
+ $vdev_count = 0;
+ }
+ elsif ( $in_config and $line =~ /^\s+raidz(\d+)?-(\d+)/ ) {
+ $current_vdev_group = "raidz" . ( $1 || "1" ) . "-$2";
+ $is_mirrored = 0;
+ $is_raidz = 1;
+ $raidz_level = $1 || 1;
+ $is_single = 0;
+ $is_striped = 0;
+ $vdev_count = 0;
+ }
+ elsif ( $in_config and $line =~ /^\s+(\S+)\s+(\S+)/ ) {
+ my $device = $1;
+ my $state = $2;
+ next
+ if ( $device eq $current_pool
+ or $device =~ /^mirror-/
+ or $device =~ /^raidz\d*-/ );
+ if ($current_vdev_group) { $vdev_count++; }
+ else { $is_single = 1; }
+ my $device_id = $device;
+ $device_id = $1 if ( $device =~ /^gpt\/(.*)/ );
+ $devices{$device} = {
+ 'pool' => $current_pool,
+ 'vdev_type' => $current_vdev_type,
+ 'is_mirrored' => $is_mirrored,
+ 'is_raidz' => $is_raidz,
+ 'raidz_level' => $raidz_level,
+ 'is_single' => $is_single,
+ 'is_striped' => $is_striped,
+ 'vdev_group' => $current_vdev_group,
+ 'vdev_count' => $vdev_count
+ };
+ $devices{"gpt/$device"} = $devices{$device}
+ if ( $device !~ /^gpt\// );
+ $devices{"/dev/$device"} = $devices{$device};
+
+ if ( $device !~ /^gpt\// ) {
+ $devices{"/dev/gpt/$device"} = $devices{$device};
+ }
+ $devices{ lc($device) } = $devices{$device};
+ if ( $device !~ /^gpt\// ) {
+ $devices{ "gpt/" . lc($device) } = $devices{$device};
+ $devices{ "/dev/gpt/" . lc($device) } = $devices{$device};
+ }
+ }
+ }
+ return ( \%pools, \%devices );
+}
+
+sub get_format_type {
+ my ($part) = @_;
+ if ( $part->{'type'} =~ /^freebsd-/ ) {
+ return get_type_description( $part->{'type'} );
+ }
+ return get_type_description( $part->{'type'} ) || $part->{'type'};
+}
+
+# Build possible ids for a partition given base device, scheme and metadata
+sub _label_conflicts_with_device {
+ my ( $label, $base_device, $scheme, $part_num, $part_name ) = @_;
+ return 0 unless defined $label && $label ne '-' && $label ne '(null)';
+ my $sep = ( $scheme && $scheme eq 'GPT' ) ? 'p' : 's';
+ my $expected =
+ ( defined $base_device && defined $part_num && length($base_device) )
+ ? "$base_device$sep$part_num"
+ : undef;
+ if ( defined $expected && lc($label) eq lc($expected) ) {
+ return 0;
+ }
+ if ( defined $part_name
+ && $part_name ne '-'
+ && lc($label) eq lc($part_name) )
+ {
+ return 0;
+ }
+
+# If a label looks like a real disk partition but doesn't match this partition,
# Do not use it for ZFS membership detection (avoids cross-disk
# misidentification).
if ( $label =~ m{^(ada|ad|da|amrd|nvd|vtbd|nda)\d+(?:p|s)\d+$}i ) {
return 1;
}
- return 0;
-}
-
-sub _possible_partition_ids {
- my ( $base_device, $scheme, $part_num, $part_name, $part_label ) = @_;
- my @ids;
- if ( defined $base_device && defined $part_num && length($base_device) ) {
- my $sep = ( $scheme && $scheme eq 'GPT' ) ? 'p' : 's';
- my $device_path = "/dev/$base_device" . $sep . $part_num;
- push( @ids, $device_path );
- ( my $short = $device_path ) =~ s/^\/dev\///;
- push( @ids, $short );
- }
- if ( $part_name && $part_name ne '-' ) {
- push( @ids, $part_name, "/dev/$part_name" );
- }
- if ( defined $part_label && $part_label ne '-' && $part_label ne '(null)' )
- {
- my $label_conflict =
- _label_conflicts_with_device( $part_label, $base_device, $scheme,
- $part_num, $part_name );
- if ($label_conflict) {
-
- # Allow only GPT label aliases to avoid false matches to device-like labels
- push( @ids,
- "gpt/$part_label", "/dev/gpt/$part_label",
- "gpt/" . lc($part_label), "/dev/gpt/" . lc($part_label) );
- }
- else {
- push( @ids,
- $part_label, "gpt/$part_label",
- "/dev/gpt/$part_label", lc($part_label),
- "gpt/" . lc($part_label), "/dev/gpt/" . lc($part_label) );
- }
- if ( $part_label =~ /^(sLOG\w+)$/ ) {
- push( @ids, $1, "gpt/$1", "/dev/gpt/$1" );
- }
- }
- return @ids;
-}
-
-# Given ids, find if present in ZFS devices cache
-sub _find_in_zfs {
- my ( $zfs_devices, @ids ) = @_;
- foreach my $id (@ids) {
- my $nid = lc($id);
- if ( $zfs_devices->{$nid} ) {
- return $zfs_devices->{$nid};
- }
- }
- return undef;
-}
-
-# Classify a partition row: returns (format, usage, role)
-sub classify_partition_row {
- my (%args) = @_;
- my $ids = [
- _possible_partition_ids(
- @args{qw/base_device scheme part_num part_name part_label/}
- )
- ];
- my $zdev = _find_in_zfs( $args{'zfs_devices'}, @$ids );
-
- # Derive type description, avoid label-as-type
- my $type_desc = $args{'entry_part_type'};
- if ( !defined $type_desc || $type_desc eq '-' || $type_desc eq 'unknown' ) {
-
- # leave undef
- }
-
+ return 0;
+}
+
+sub _possible_partition_ids {
+ my ( $base_device, $scheme, $part_num, $part_name, $part_label ) = @_;
+ my @ids;
+ if ( defined $base_device && defined $part_num && length($base_device) ) {
+ my $sep = ( $scheme && $scheme eq 'GPT' ) ? 'p' : 's';
+ my $device_path = "/dev/$base_device" . $sep . $part_num;
+ push( @ids, $device_path );
+ ( my $short = $device_path ) =~ s/^\/dev\///;
+ push( @ids, $short );
+ }
+ if ( $part_name && $part_name ne '-' ) {
+ push( @ids, $part_name, "/dev/$part_name" );
+ }
+ if ( defined $part_label && $part_label ne '-' && $part_label ne '(null)' )
+ {
+ my $label_conflict =
+ _label_conflicts_with_device( $part_label, $base_device, $scheme,
+ $part_num, $part_name );
+ if ($label_conflict) {
+
+ # Allow only GPT label aliases to avoid false matches to device-like labels
+ push( @ids,
+ "gpt/$part_label", "/dev/gpt/$part_label",
+ "gpt/" . lc($part_label), "/dev/gpt/" . lc($part_label) );
+ }
+ else {
+ push( @ids,
+ $part_label, "gpt/$part_label",
+ "/dev/gpt/$part_label", lc($part_label),
+ "gpt/" . lc($part_label), "/dev/gpt/" . lc($part_label) );
+ }
+ if ( $part_label =~ /^(sLOG\w+)$/ ) {
+ push( @ids, $1, "gpt/$1", "/dev/gpt/$1" );
+ }
+ }
+ return @ids;
+}
+
+# Given ids, find if present in ZFS devices cache
+sub _find_in_zfs {
+ my ( $zfs_devices, @ids ) = @_;
+ foreach my $id (@ids) {
+ my $nid = lc($id);
+ if ( $zfs_devices->{$nid} ) {
+ return $zfs_devices->{$nid};
+ }
+ }
+ return undef;
+}
+
+# Classify a partition row: returns (format, usage, role)
+sub classify_partition_row {
+ my (%args) = @_;
+ my $ids = [
+ _possible_partition_ids(
+ @args{qw/base_device scheme part_num part_name part_label/}
+ )
+ ];
+ my $zdev = _find_in_zfs( $args{'zfs_devices'}, @$ids );
+
+ # Derive type description, avoid label-as-type
+ my $type_desc = $args{'entry_part_type'};
+ if ( !defined $type_desc || $type_desc eq '-' || $type_desc eq 'unknown' ) {
+
+ # leave undef
+ }
+
# Avoid clearing real types (like 'efi' or 'freebsd-boot') when label
# text matches by case.
# Only drop if the "type" clearly looks like a provider/label path that
# mirrors the label.
- if ( defined $type_desc && defined $args{'part_label'} ) {
- my $pl = $args{'part_label'};
- if ( $type_desc =~ m{^(?:/dev/)?gpt(?:id)?/\Q$pl\E$}i ) {
- undef $type_desc;
- }
- }
-
- my ( $format, $usage, $role ) = ( '-', $text{'part_nouse'}, '-' );
-
+ if ( defined $type_desc && defined $args{'part_label'} ) {
+ my $pl = $args{'part_label'};
+ if ( $type_desc =~ m{^(?:/dev/)?gpt(?:id)?/\Q$pl\E$}i ) {
+ undef $type_desc;
+ }
+ }
+
+ my ( $format, $usage, $role ) = ( '-', $text{'part_nouse'}, '-' );
+
# Explicit boot detection based on GPT GUIDs and MBR hex codes or
# human-readable type
- my $raw = lc( $args{'entry_rawtype'} || '' );
- my $t = lc( $type_desc || '' );
- my %boot_guid = map { $_ => 1 } qw(
- c12a7328-f81f-11d2-ba4b-00a0c93ec93b # EFI System
- 21686148-6449-6e6f-744e-656564454649 # BIOS Boot (GRUB BIOS)
- 83bd6b9d-7f41-11dc-be0b-001560b84f0f # FreeBSD Boot
- 49f48d5a-b10e-11dc-b99b-0019d1879648 # NetBSD boot
- 824cc7a0-36a8-11e3-890a-952519ad3f61 # OpenBSD boot
- 426f6f74-0000-11aa-aa11-00306543ecac # Apple Boot
- );
- my %boot_mbr = map { $_ => 1 } qw( 0xef 0xa0 0xa5 0xa6 0xa9 0xab );
- my $is_boot_type =
- ( $t =~
-/\b(efi|bios-?boot|freebsd-boot|netbsd-boot|openbsd-boot|apple-boot)\b/
- );
- my $is_boot_raw = ( $raw && ( $boot_guid{$raw} || $boot_mbr{$raw} ) );
-
- if ( $is_boot_type || $is_boot_raw ) {
- my $fmt =
- ( $t =~ /efi/
- || $raw eq 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b'
- || lc($raw) eq '0xef' )
- ? get_type_description('efi')
- : get_type_description('freebsd-boot');
-
-# Access text properly - %text is in main namespace when this is called from CGI
- my $boot_txt = $text{'disk_boot'};
- my $role_txt = $text{'disk_boot_role'};
- return ( $fmt, $boot_txt, $role_txt );
- }
-
- # Heuristic fallback only if no explicit identifiers are present
- if (
- (
- !$args{'entry_part_type'}
- || $args{'entry_part_type'} eq '-'
- || $args{'entry_part_type'} eq 'unknown'
- )
- && ( $args{'part_num'} || '' ) eq '1'
- )
- {
- my $sb = $args{'size_blocks'} || 0;
- if ( $sb > 0 ) {
- my $base =
- base_disk_device( '/dev/' . ( $args{'base_device'} || '' ) );
- my $ss = get_disk_sectorsize($base) || 512;
- my $bytes = $sb * $ss;
- if ( $bytes <= 2 * 1024 * 1024 ) { # <= 2MiB
- return ( get_type_description('freebsd-boot'),
- $text{'disk_boot'}, $text{'disk_boot_role'} );
- }
- }
- elsif ($args{'size_human'}
- && $args{'size_human'} =~ /^(?:512k|1m|1\.0m)$/i )
- {
- return ( get_type_description('freebsd-boot'),
- $text{'disk_boot'}, $text{'disk_boot_role'} );
- }
- }
- if ($zdev) {
- $format = 'FreeBSD ZFS';
- my $inzfs_txt = $text{'disk_inzfs'};
- my $z_mirror = $text{'disk_zfs_mirror'};
- my $z_stripe = $text{'disk_zfs_stripe'};
- my $z_single = $text{'disk_zfs_single'};
- my $z_data = $text{'disk_zfs_data'};
- my $z_log = $text{'disk_zfs_log'};
- my $z_cache = $text{'disk_zfs_cache'};
- my $z_spare = $text{'disk_zfs_spare'};
- $usage = $inzfs_txt . ' ' . $zdev->{'pool'};
- my $vt = $zdev->{'vdev_type'};
- my $cnt = $zdev->{'vdev_count'} || 0;
- if ( $vt eq 'log' ) { $role = $z_log; }
- elsif ( $vt eq 'cache' ) { $role = $z_cache; }
- elsif ( $vt eq 'spare' ) { $role = $z_spare; }
- elsif ( $zdev->{'is_mirrored'} ) {
- $role = $z_mirror;
- $role .= " ($cnt in group)" if $cnt;
- }
- elsif ( $zdev->{'is_raidz'} ) {
- my $lvl = $zdev->{'raidz_level'} || 1;
- $role = 'RAID-Z' . $lvl;
- $role .= " ($cnt in group)" if $cnt;
- }
- elsif ( $zdev->{'is_striped'} ) {
- $role = $z_stripe;
- $role .= " ($cnt in group)" if $cnt;
- }
- elsif ( $zdev->{'is_single'} ) { $role = $z_single; }
- else { $role = $z_data; }
- return ( $format, $usage, $role );
- }
-
+ my $raw = lc( $args{'entry_rawtype'} || '' );
+ my $t = lc( $type_desc || '' );
+ my %boot_guid = map { $_ => 1 } qw(
+ c12a7328-f81f-11d2-ba4b-00a0c93ec93b # EFI System
+ 21686148-6449-6e6f-744e-656564454649 # BIOS Boot (GRUB BIOS)
+ 83bd6b9d-7f41-11dc-be0b-001560b84f0f # FreeBSD Boot
+ 49f48d5a-b10e-11dc-b99b-0019d1879648 # NetBSD boot
+ 824cc7a0-36a8-11e3-890a-952519ad3f61 # OpenBSD boot
+ 426f6f74-0000-11aa-aa11-00306543ecac # Apple Boot
+ );
+ my %boot_mbr = map { $_ => 1 } qw( 0xef 0xa0 0xa5 0xa6 0xa9 0xab );
+ my $is_boot_type =
+ ( $t =~
+/\b(efi|bios-?boot|freebsd-boot|netbsd-boot|openbsd-boot|apple-boot)\b/
+ );
+ my $is_boot_raw = ( $raw && ( $boot_guid{$raw} || $boot_mbr{$raw} ) );
+
+ if ( $is_boot_type || $is_boot_raw ) {
+ my $fmt =
+ ( $t =~ /efi/
+ || $raw eq 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b'
+ || lc($raw) eq '0xef' )
+ ? get_type_description('efi')
+ : get_type_description('freebsd-boot');
+
+# Access text properly - %text is in main namespace when this is called from CGI
+ my $boot_txt = $text{'disk_boot'};
+ my $role_txt = $text{'disk_boot_role'};
+ return ( $fmt, $boot_txt, $role_txt );
+ }
+
+ # Heuristic fallback only if no explicit identifiers are present
+ if (
+ (
+ !$args{'entry_part_type'}
+ || $args{'entry_part_type'} eq '-'
+ || $args{'entry_part_type'} eq 'unknown'
+ )
+ && ( $args{'part_num'} || '' ) eq '1'
+ )
+ {
+ my $sb = $args{'size_blocks'} || 0;
+ if ( $sb > 0 ) {
+ my $base =
+ base_disk_device( '/dev/' . ( $args{'base_device'} || '' ) );
+ my $ss = get_disk_sectorsize($base) || 512;
+ my $bytes = $sb * $ss;
+ if ( $bytes <= 2 * 1024 * 1024 ) { # <= 2MiB
+ return ( get_type_description('freebsd-boot'),
+ $text{'disk_boot'}, $text{'disk_boot_role'} );
+ }
+ }
+ elsif ($args{'size_human'}
+ && $args{'size_human'} =~ /^(?:512k|1m|1\.0m)$/i )
+ {
+ return ( get_type_description('freebsd-boot'),
+ $text{'disk_boot'}, $text{'disk_boot_role'} );
+ }
+ }
+ if ($zdev) {
+ $format = 'FreeBSD ZFS';
+ my $inzfs_txt = $text{'disk_inzfs'};
+ my $z_mirror = $text{'disk_zfs_mirror'};
+ my $z_stripe = $text{'disk_zfs_stripe'};
+ my $z_single = $text{'disk_zfs_single'};
+ my $z_data = $text{'disk_zfs_data'};
+ my $z_log = $text{'disk_zfs_log'};
+ my $z_cache = $text{'disk_zfs_cache'};
+ my $z_spare = $text{'disk_zfs_spare'};
+ $usage = $inzfs_txt . ' ' . $zdev->{'pool'};
+ my $vt = $zdev->{'vdev_type'};
+ my $cnt = $zdev->{'vdev_count'} || 0;
+ if ( $vt eq 'log' ) { $role = $z_log; }
+ elsif ( $vt eq 'cache' ) { $role = $z_cache; }
+ elsif ( $vt eq 'spare' ) { $role = $z_spare; }
+ elsif ( $zdev->{'is_mirrored'} ) {
+ $role = $z_mirror;
+ $role .= " ($cnt in group)" if $cnt;
+ }
+ elsif ( $zdev->{'is_raidz'} ) {
+ my $lvl = $zdev->{'raidz_level'} || 1;
+ $role = 'RAID-Z' . $lvl;
+ $role .= " ($cnt in group)" if $cnt;
+ }
+ elsif ( $zdev->{'is_striped'} ) {
+ $role = $z_stripe;
+ $role .= " ($cnt in group)" if $cnt;
+ }
+ elsif ( $zdev->{'is_single'} ) { $role = $z_single; }
+ else { $role = $z_data; }
+ return ( $format, $usage, $role );
+ }
+
# Not in ZFS: infer by type_desc, rawtype and size heuristic (this
# shouldn't be reached for boot, but keep as fallback)
- if ( defined $type_desc && $type_desc =~ /(?:freebsd|linux)-swap/i ) {
- $format = 'Swap';
- $usage = $text{'disk_swap'};
- $role = $text{'disk_swap_role'};
- }
- elsif ( defined $type_desc && $type_desc =~ /linux-lvm/i ) {
- $format = 'Linux LVM';
- }
- elsif ( defined $type_desc && $type_desc =~ /linux-raid/i ) {
- $format = 'Linux RAID';
- }
-
- # Recognize common FAT/NTFS identifiers on both GPT and MBR
- elsif ( defined $type_desc && $type_desc =~ /ntfs/i ) { $format = 'NTFS'; }
- elsif ( defined $type_desc && $type_desc =~ /fat32/i ) {
- $format = 'FAT32';
- }
- elsif ( defined $type_desc && $type_desc =~ /fat|msdos/i ) {
- $format = 'FAT';
- }
- elsif ( defined $type_desc && $type_desc =~ /ms-basic/i ) {
- $format = 'FAT/NTFS';
- }
- elsif ( $raw ne '' && $raw =~ /^\d+$/ ) {
-
- # MBR raw type codes: 7=NTFS, 6/11/12/14 = FAT variants
- my $code = int($raw);
- if ( $code == 7 ) { $format = 'NTFS'; }
- elsif ( $code == 11 || $code == 12 ) { $format = 'FAT32'; }
- elsif ( $code == 6 || $code == 14 ) { $format = 'FAT'; }
- }
- elsif ( defined $type_desc && $type_desc =~ /linux/i ) {
- $format = 'Linux';
- }
- elsif ( defined $type_desc && $type_desc =~ /apple-ufs/i ) {
- $format = 'Apple UFS';
- }
- elsif ( defined $type_desc && $type_desc =~ /apple-hfs/i ) {
- $format = 'HFS+';
- }
- elsif ( defined $type_desc && $type_desc =~ /apple-raid/i ) {
- $format = 'Apple RAID';
- }
- elsif ( defined $type_desc && $type_desc =~ /freebsd-ufs/i ) {
- $format = 'FreeBSD UFS';
- }
- elsif ( defined $type_desc && $type_desc =~ /freebsd-zfs/i ) {
- $format = 'FreeBSD ZFS';
- }
- else {
- if ( defined $args{'part_label'}
- && $args{'part_label'} =~ /^swap\d*$/i )
- {
- $format = 'Swap';
- $usage = $text{'disk_swap'};
- $role = $text{'disk_swap_role'};
- }
- }
- return ( $format, $usage, $role );
-}
-
-# list_partition_types()
-# Returns a list suitable for ui_select: [ [ value, label ], ... ]
-# Adapts to whether the system uses gpart (GPT) or legacy disklabel.
-sub list_partition_types {
- my ($scheme) = @_;
- if ( is_using_gpart() ) {
-
-# BSD-on-MBR inner label (used when creating sub-partitions inside an MBR slice)
- if ( defined $scheme && $scheme =~ /BSD/i ) {
- return (
- [ 'freebsd-ufs', get_type_description('freebsd-ufs') ],
- [ 'freebsd-zfs', get_type_description('freebsd-zfs') ],
- [ 'freebsd-swap', get_type_description('freebsd-swap') ],
- [ 'freebsd-vinum', get_type_description('freebsd-vinum') ],
- );
- }
-
+ if ( defined $type_desc && $type_desc =~ /(?:freebsd|linux)-swap/i ) {
+ $format = 'Swap';
+ $usage = $text{'disk_swap'};
+ $role = $text{'disk_swap_role'};
+ }
+ elsif ( defined $type_desc && $type_desc =~ /linux-lvm/i ) {
+ $format = 'Linux LVM';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /linux-raid/i ) {
+ $format = 'Linux RAID';
+ }
+
+ # Recognize common FAT/NTFS identifiers on both GPT and MBR
+ elsif ( defined $type_desc && $type_desc =~ /ntfs/i ) { $format = 'NTFS'; }
+ elsif ( defined $type_desc && $type_desc =~ /fat32/i ) {
+ $format = 'FAT32';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /fat|msdos/i ) {
+ $format = 'FAT';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /ms-basic/i ) {
+ $format = 'FAT/NTFS';
+ }
+ elsif ( $raw ne '' && $raw =~ /^\d+$/ ) {
+
+ # MBR raw type codes: 7=NTFS, 6/11/12/14 = FAT variants
+ my $code = int($raw);
+ if ( $code == 7 ) { $format = 'NTFS'; }
+ elsif ( $code == 11 || $code == 12 ) { $format = 'FAT32'; }
+ elsif ( $code == 6 || $code == 14 ) { $format = 'FAT'; }
+ }
+ elsif ( defined $type_desc && $type_desc =~ /linux/i ) {
+ $format = 'Linux';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /apple-ufs/i ) {
+ $format = 'Apple UFS';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /apple-hfs/i ) {
+ $format = 'HFS+';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /apple-raid/i ) {
+ $format = 'Apple RAID';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /freebsd-ufs/i ) {
+ $format = 'FreeBSD UFS';
+ }
+ elsif ( defined $type_desc && $type_desc =~ /freebsd-zfs/i ) {
+ $format = 'FreeBSD ZFS';
+ }
+ else {
+ if ( defined $args{'part_label'}
+ && $args{'part_label'} =~ /^swap\d*$/i )
+ {
+ $format = 'Swap';
+ $usage = $text{'disk_swap'};
+ $role = $text{'disk_swap_role'};
+ }
+ }
+ return ( $format, $usage, $role );
+}
+
+# list_partition_types()
+# Returns a list suitable for ui_select: [ [ value, label ], ... ]
+# Adapts to whether the system uses gpart (GPT) or legacy disklabel.
+sub list_partition_types {
+ my ($scheme) = @_;
+ if ( is_using_gpart() ) {
+
+# BSD-on-MBR inner label (used when creating sub-partitions inside an MBR slice)
+ if ( defined $scheme && $scheme =~ /BSD/i ) {
+ return (
+ [ 'freebsd-ufs', get_type_description('freebsd-ufs') ],
+ [ 'freebsd-zfs', get_type_description('freebsd-zfs') ],
+ [ 'freebsd-swap', get_type_description('freebsd-swap') ],
+ [ 'freebsd-vinum', get_type_description('freebsd-vinum') ],
+ );
+ }
+
# If outer scheme is not GPT (e.g. MBR), present MBR partition types for
# top-level slices
- if ( defined $scheme && $scheme !~ /GPT/i ) {
- my @mbr_types = (
- [ 'freebsd', get_type_description('freebsd') ],
- [ 'fat32lba', 'FAT32 (LBA)' ],
- [ 'fat32', 'FAT32' ],
- [ 'fat16', 'FAT16' ],
- [ 'ntfs', 'NTFS' ],
- [ 'linux', 'Linux' ],
- [ 'linux-swap', get_type_description('linux-swap') ],
- [ 'efi', get_type_description('efi') ],
- );
- return @mbr_types;
- }
-
- # Default GPT types (common, practical choices)
- my @gpt_types = (
- [ 'efi', get_type_description('efi') ],
- [ 'bios-boot', get_type_description('bios-boot') ],
- [ 'freebsd-boot', get_type_description('freebsd-boot') ],
- [ 'freebsd-zfs', get_type_description('freebsd-zfs') ],
- [ 'freebsd-ufs', get_type_description('freebsd-ufs') ],
- [ 'freebsd-swap', get_type_description('freebsd-swap') ],
- [ 'freebsd-vinum', get_type_description('freebsd-vinum') ],
- [ 'ms-basic-data', get_type_description('ms-basic-data') ],
- [ 'ms-reserved', get_type_description('ms-reserved') ],
- [ 'ms-recovery', get_type_description('ms-recovery') ],
- [ 'linux-data', get_type_description('linux-data') ],
- [ 'linux-swap', get_type_description('linux-swap') ],
- [ 'linux-lvm', get_type_description('linux-lvm') ],
- [ 'linux-raid', get_type_description('linux-raid') ],
- [ 'apple-boot', get_type_description('apple-boot') ],
- [ 'apple-hfs', get_type_description('apple-hfs') ],
- [ 'apple-ufs', get_type_description('apple-ufs') ],
- [ 'apple-raid', get_type_description('apple-raid') ],
- [ 'apple-label', get_type_description('apple-label') ],
- );
- return @gpt_types;
- }
- else {
- # Legacy BSD disklabel types
- my @label_types = (
- [ '4.2BSD', 'FreeBSD UFS' ],
- [ 'swap', 'Swap' ],
- [ 'unused', 'Unused' ],
- [ 'vinum', 'FreeBSD Vinum' ],
- );
- return @label_types;
- }
-}
-
-sub get_partition_role {
- my ($part) = @_;
- if ( is_boot_partition($part) ) { return $text{'part_boot'}; }
- my $zfs_info = get_all_zfs_info();
- if ( $zfs_info->{ $part->{'device'} }
- and $zfs_info->{ $part->{'device'} } =~ /\(log\)$/ )
- {
- return $text{'part_zfslog'};
- }
- if ( $zfs_info->{ $part->{'device'} } ) {
- return $text{'part_zfsdata'};
- }
- my @mounts = mount::list_mounted();
- foreach my $m (@mounts) {
- if ( $m->[0] eq $part->{'device'} ) {
- return text( 'part_mounted', $m->[1] );
- }
- }
- return $text{'part_unused'};
-}
-
-sub get_detailed_disk_info {
- my ($device) = @_;
- my $info = {};
- ( my $dev_name = $device ) =~ s/^\/dev\///;
- my $out = backquote_command(
- "geom disk list " . quote_path($dev_name) . " 2>/dev/null" );
- return undef if ($?);
- foreach my $line ( split( /\n/, $out ) ) {
- if ( $line =~ /^\s+Mediasize:\s+(\d+)\s+\(([^)]+)\)/ ) {
- $info->{'mediasize_bytes'} = $1;
- $info->{'mediasize'} = $2;
- }
- elsif ( $line =~ /^\s+Sectorsize:\s+(\d+)/ ) {
- $info->{'sectorsize'} = $1;
- }
- elsif ( $line =~ /^\s+Stripesize:\s+(\d+)/ ) {
- $info->{'stripesize'} = $1;
- }
- elsif ( $line =~ /^\s+Stripeoffset:\s+(\d+)/ ) {
- $info->{'stripeoffset'} = $1;
- }
- elsif ( $line =~ /^\s+Mode:\s+(.*)/ ) {
- $info->{'mode'} = $1;
- }
- elsif ( $line =~ /^\s+rotationrate:\s+(\d+)/ ) {
- $info->{'rotationrate'} = $1;
- }
- elsif ( $line =~ /^\s+ident:\s+(.*)/ ) {
- $info->{'ident'} = $1;
- }
- elsif ( $line =~ /^\s+lunid:\s+(.*)/ ) {
- $info->{'lunid'} = $1;
- }
- elsif ( $line =~ /^\s+descr:\s+(.*)/ ) {
- $info->{'descr'} = $1;
- }
- }
- return $info;
-}
-
+ if ( defined $scheme && $scheme !~ /GPT/i ) {
+ my @mbr_types = (
+ [ 'freebsd', get_type_description('freebsd') ],
+ [ 'fat32lba', 'FAT32 (LBA)' ],
+ [ 'fat32', 'FAT32' ],
+ [ 'fat16', 'FAT16' ],
+ [ 'ntfs', 'NTFS' ],
+ [ 'linux', 'Linux' ],
+ [ 'linux-swap', get_type_description('linux-swap') ],
+ [ 'efi', get_type_description('efi') ],
+ );
+ return @mbr_types;
+ }
+
+ # Default GPT types (common, practical choices)
+ my @gpt_types = (
+ [ 'efi', get_type_description('efi') ],
+ [ 'bios-boot', get_type_description('bios-boot') ],
+ [ 'freebsd-boot', get_type_description('freebsd-boot') ],
+ [ 'freebsd-zfs', get_type_description('freebsd-zfs') ],
+ [ 'freebsd-ufs', get_type_description('freebsd-ufs') ],
+ [ 'freebsd-swap', get_type_description('freebsd-swap') ],
+ [ 'freebsd-vinum', get_type_description('freebsd-vinum') ],
+ [ 'ms-basic-data', get_type_description('ms-basic-data') ],
+ [ 'ms-reserved', get_type_description('ms-reserved') ],
+ [ 'ms-recovery', get_type_description('ms-recovery') ],
+ [ 'linux-data', get_type_description('linux-data') ],
+ [ 'linux-swap', get_type_description('linux-swap') ],
+ [ 'linux-lvm', get_type_description('linux-lvm') ],
+ [ 'linux-raid', get_type_description('linux-raid') ],
+ [ 'apple-boot', get_type_description('apple-boot') ],
+ [ 'apple-hfs', get_type_description('apple-hfs') ],
+ [ 'apple-ufs', get_type_description('apple-ufs') ],
+ [ 'apple-raid', get_type_description('apple-raid') ],
+ [ 'apple-label', get_type_description('apple-label') ],
+ );
+ return @gpt_types;
+ }
+ else {
+ # Legacy BSD disklabel types
+ my @label_types = (
+ [ '4.2BSD', 'FreeBSD UFS' ],
+ [ 'swap', 'Swap' ],
+ [ 'unused', 'Unused' ],
+ [ 'vinum', 'FreeBSD Vinum' ],
+ );
+ return @label_types;
+ }
+}
+
+sub get_partition_role {
+ my ($part) = @_;
+ if ( is_boot_partition($part) ) { return $text{'part_boot'}; }
+ my $zfs_info = get_all_zfs_info();
+ if ( $zfs_info->{ $part->{'device'} }
+ and $zfs_info->{ $part->{'device'} } =~ /\(log\)$/ )
+ {
+ return $text{'part_zfslog'};
+ }
+ if ( $zfs_info->{ $part->{'device'} } ) {
+ return $text{'part_zfsdata'};
+ }
+ my @mounts = mount::list_mounted();
+ foreach my $m (@mounts) {
+ if ( $m->[0] eq $part->{'device'} ) {
+ return text( 'part_mounted', $m->[1] );
+ }
+ }
+ return $text{'part_unused'};
+}
+
+sub get_detailed_disk_info {
+ my ($device) = @_;
+ my $info = {};
+ ( my $dev_name = $device ) =~ s/^\/dev\///;
+ my $out = backquote_command(
+ "geom disk list " . quote_path($dev_name) . " 2>/dev/null" );
+ return undef if ($?);
+ foreach my $line ( split( /\n/, $out ) ) {
+ if ( $line =~ /^\s+Mediasize:\s+(\d+)\s+\(([^)]+)\)/ ) {
+ $info->{'mediasize_bytes'} = $1;
+ $info->{'mediasize'} = $2;
+ }
+ elsif ( $line =~ /^\s+Sectorsize:\s+(\d+)/ ) {
+ $info->{'sectorsize'} = $1;
+ }
+ elsif ( $line =~ /^\s+Stripesize:\s+(\d+)/ ) {
+ $info->{'stripesize'} = $1;
+ }
+ elsif ( $line =~ /^\s+Stripeoffset:\s+(\d+)/ ) {
+ $info->{'stripeoffset'} = $1;
+ }
+ elsif ( $line =~ /^\s+Mode:\s+(.*)/ ) {
+ $info->{'mode'} = $1;
+ }
+ elsif ( $line =~ /^\s+rotationrate:\s+(\d+)/ ) {
+ $info->{'rotationrate'} = $1;
+ }
+ elsif ( $line =~ /^\s+ident:\s+(.*)/ ) {
+ $info->{'ident'} = $1;
+ }
+ elsif ( $line =~ /^\s+lunid:\s+(.*)/ ) {
+ $info->{'lunid'} = $1;
+ }
+ elsif ( $line =~ /^\s+descr:\s+(.*)/ ) {
+ $info->{'descr'} = $1;
+ }
+ }
+ return $info;
+}
+
sub initialize_slice {
my ( $disk, $slice ) = @_;
# If the outer disk is GPT, we are not creating inner BSD labels
if ( is_using_gpart() ) {
- my $base = $disk->{'device'};
- $base =~ s{^/dev/}{};
- my $ds = get_disk_structure($base);
- if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
- return undef;
+ my $base = $disk->{'device'};
+ $base =~ s{^/dev/}{};
+ my $ds = get_disk_structure($base);
+ if ( $ds && $ds->{'scheme'} && $ds->{'scheme'} =~ /GPT/i ) {
+ return undef;
}
}
@@ -2247,69 +2251,69 @@ sub initialize_slice {
return "Failed to initialize slice: $werr" if $werr;
return undef;
}
-
-# ---------------------------------------------------------------------
-# partition_select(name, value)
-# Provide a selector for partitions/slices for external modules (e.g., mount)
-# Returns an HTML