#!/usr/local/bin/perl # Show a form to create a filesystem on a partition use strict; use warnings; no warnings 'redefine'; no warnings 'uninitialized'; require './bsdfdisk-lib.pl'; our ( %in, %text, $module_name ); &ReadParse(); # Get the disk and slice # Validate input parameters to prevent command injection $in{'device'} =~ /^[a-zA-Z0-9_\/.-]+$/ or &error("Invalid device name"); $in{'device'} !~ /\.\./ or &error("Invalid device name"); $in{'slice'} =~ /^\d+$/ or &error("Invalid slice number") if $in{'slice'}; $in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter") if $in{'part'}; my @disks = &list_disks_partitions(); my ($disk) = grep { $_->{'device'} eq $in{'device'} } @disks; $disk || &error( $text{'disk_egone'} ); my ($slice) = grep { $_->{'number'} eq $in{'slice'} } @{ $disk->{'slices'} }; $slice || &error( $text{'slice_egone'} ); my $object; my $url_device = &urlize( $in{'device'} ); my $url_slice = &urlize( $in{'slice'} ); my $url_part = &urlize( $in{'part'} ); if ( $in{'part'} ne '' ) { $in{'part'} =~ /^[a-z]$/ or &error("Invalid partition letter"); my ($part) = grep { $_->{'letter'} eq $in{'part'} } @{ $slice->{'parts'} }; $part || &error( $text{'part_egone'} ); $object = $part; } else { $object = $slice; } # If this device is part of a ZFS pool, offer dataset creation instead my $zdev = get_zfs_device_info($object); if ($zdev) { &ui_print_header( $object->{'desc'}, $text{'newfs_title'}, "" ); # Parent pool summary (best effort) my $parent = $zdev->{'pool'}; my $parent_q = quote_path($parent); my $zlist = &backquote_command( "zfs list -H -o name,used,avail,refer,mountpoint $parent_q 2>/dev/null" ); if ($zlist) { chomp($zlist); my @cols = split( /\t/, $zlist ); if ( @cols >= 5 ) { print "$text{'newfs_zfs_parent'}\n"; print ui_columns_start( [ $text{'newfs_zfs_fs'}, $text{'newfs_zfs_used'}, $text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'}, $text{'newfs_zfs_mount'} ] ); print ui_columns_row( [ map { html_escape($_) } @cols[ 0 .. 4 ] ] ); print ui_columns_end(); print "
\n"; } } # Existing filesystems under this pool my $zlist_fs = &backquote_command( "zfs list -H -r -t filesystem -o name,used,avail,refer,mountpoint $parent_q 2>/dev/null" ); if ($zlist_fs) { my @lines = split( /\n/, $zlist_fs ); if ( @lines && $lines[0] =~ /^\Q$parent\E(\t|$)/ ) { shift @lines; } if (@lines) { print "$text{'newfs_zfs_existing'}\n"; print ui_columns_start( [ $text{'newfs_zfs_fs'}, $text{'newfs_zfs_used'}, $text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'}, $text{'newfs_zfs_mount'} ] ); foreach my $ln (@lines) { my @cols = split( /\t/, $ln ); next unless @cols >= 5; print ui_columns_row( [ map { html_escape($_) } @cols[ 0 .. 4 ] ] ); } print ui_columns_end(); print "
\n"; } } # Existing volumes under this pool my $zlist_vol = &backquote_command( "zfs list -H -r -t volume -o name,used,avail,refer,volsize $parent_q 2>/dev/null" ); if ($zlist_vol) { my @lines = split( /\n/, $zlist_vol ); if ( @lines && $lines[0] =~ /^\Q$parent\E(\t|$)/ ) { shift @lines; } if (@lines) { print "$text{'newfs_zvol_existing'}\n"; print ui_columns_start( [ $text{'newfs_zvol_vol'}, $text{'newfs_zfs_used'}, $text{'newfs_zfs_avail'}, $text{'newfs_zfs_refer'}, $text{'newfs_zvol_volsize'} ] ); foreach my $ln (@lines) { my @cols = split( /\t/, $ln ); next unless @cols >= 5; print ui_columns_row( [ map { html_escape($_) } @cols[ 0 .. 4 ] ] ); } print ui_columns_end(); print "
\n"; } } my %fs_descriptions = ( 'recordsize' => { '128K' => '128K (General/Default)', '1M' => '1M (Media/Large files)', '4M' => '4M', '16K' => '16K (Database)', '4K' => '4K (VM)' }, 'compression' => { 'lz4' => 'lz4 (Recommended)', 'off' => 'off (None)', 'gzip' => 'gzip (High compression)' }, 'atime' => { 'off' => 'off (Performance)', 'on' => 'on (Record access time)' }, 'sync' => { 'disabled' => 'disabled (Performance)', 'standard' => 'standard (Safety)', 'always' => 'always (Maximum Safety)' }, 'acltype' => { 'nfsv4' => 'nfsv4 (ZFS Default)', 'posixacl' => 'posixacl (Linux Default)' }, 'aclinherit' => { 'passthrough' => 'passthrough (SMB Recommended)', 'restricted' => 'restricted (ZFS Default)' }, 'aclmode' => { 'passthrough' => 'passthrough (SMB Recommended)', 'discard' => 'discard (ZFS Default)' } ); my @fs_order = ( 'recordsize', 'compression', 'atime', 'sync', 'exec', 'canmount', 'acltype', 'aclinherit', 'aclmode', 'xattr' ); my %fs_defaults = ( 'recordsize' => '128K', 'compression' => 'lz4', 'atime' => 'off', 'sync' => 'default', 'acltype' => 'nfsv4', 'aclinherit' => 'passthrough', 'aclmode' => 'passthrough', 'canmount' => 'on', 'exec' => 'on', 'xattr' => 'sa', ); my %fs_opts = ( 'recordsize' => '512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K, 256K, 512K, 1M', 'compression' => 'on, off, lz4, gzip', 'atime' => 'on, off', 'sync' => 'standard, always, disabled', 'exec' => 'on, off', 'canmount' => 'on, off, noauto', 'acltype' => 'nfsv4, posixacl', 'aclinherit' => 'discard, noallow, restricted, passthrough, passthrough-x', 'aclmode' => 'discard, groupmask, passthrough', 'xattr' => 'on, off, sa', ); my %acl_tooltips = ( 'acltype' => $text{'newfs_zfs_acltype_desc'}, 'aclinherit' => $text{'newfs_zfs_aclinherit_desc'}, 'aclmode' => $text{'newfs_zfs_aclmode_desc'}, ); my $base_url = "newfs_form.cgi?device=" . $url_device . "&slice=$url_slice"; $base_url .= "&part=$url_part" if ( $in{'part'} ne '' ); my @tabs = ( [ "zfs", $text{'newfs_zfs_tab_fs'}, $base_url . "&mode=zfs" ], [ "zvol", $text{'newfs_zfs_tab_vol'}, $base_url . "&mode=zvol" ], ); print &ui_tabs_start( \@tabs, "mode", $in{'mode'} || $tabs[0]->[0], 1 ); print &ui_tabs_start_tab( "mode", "zfs" ); print &ui_form_start( "zfs_create.cgi", "post", undef, "onsubmit='return validateFsForm(this)'" ); print &ui_hidden( "device", $in{'device'} ); print &ui_hidden( "slice", $in{'slice'} ); print &ui_hidden( "part", $in{'part'} ); print &ui_hidden( "parent", $parent ); print &ui_table_start( $text{'newfs_zfs_header'}, 'width=100%', 2 ); print &ui_table_row( $text{'newfs_zfs_name'}, html_escape($parent) . "/" . &ui_textbox( "zfs", undef, 24 ) ); print &ui_table_row( $text{'newfs_zfs_mountpoint'}, &ui_filebox( 'mountpoint', '', 25, undef, undef, 1 ) . " (" . $text{'newfs_zfs_mount_blank'} . ")" ); print &ui_table_end(); print &ui_table_start( $text{'newfs_zfs_opts'}, "width=100%", undef ); foreach my $key (@fs_order) { my @options; push( @options, [ 'default', 'default' ] ); foreach my $opt ( split( ", ", $fs_opts{$key} ) ) { my $label = $fs_descriptions{$key}{$opt} || $opt; push( @options, [ $opt, $label ] ); } my $default_val = $fs_defaults{$key} || 'default'; my $help = $acl_tooltips{$key} ? "
$acl_tooltips{$key}" : ""; my $selected = defined( $in{$key} ) ? $in{$key} : $default_val; print ui_table_row( $key . ': ', ui_select( $key, $selected, \@options, 1, 0, 1 ) . $help ); } my $add_inherit_default = defined( $in{'add_inherit'} ) ? $in{'add_inherit'} : 1; print ui_table_row( $text{'newfs_zfs_aclflags'}, ui_checkbox( 'add_inherit', 1, $text{'newfs_zfs_aclflags_desc'}, $add_inherit_default ) ); print &ui_table_end(); print &ui_form_end( [ [ undef, $text{'create'} ] ] ); print &ui_tabs_end_tab( "mode", "zfs" ); # ZVOL tab print &ui_tabs_start_tab( "mode", "zvol" ); my %zvol_defaults = ( 'volblocksize' => '16K', 'compression' => 'lz4', 'sync' => 'default', 'logbias' => 'latency', 'primarycache' => 'all', 'secondarycache' => 'all', ); my %zvol_opts = ( 'volblocksize' => '512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K', 'compression' => 'on, off, lz4, gzip', 'sync' => 'standard, always, disabled', 'logbias' => 'latency, throughput', 'primarycache' => 'all, metadata, none', 'secondarycache' => 'all, metadata, none', ); my %zvol_desc = ( 'volblocksize' => { '512' => '512B', '1K' => '1K', '2K' => '2K', '4K' => '4K (Swap)', '8K' => '8K (Databases)', '16K' => '16K (VM/Default)', '64K' => '64K (Backups)', }, 'logbias' => { 'latency' => 'latency (databases, NFS sync)', 'throughput' => 'throughput (VM, media/backups)', }, 'primarycache' => { 'all' => 'all (filesystems)', 'metadata' => 'metadata (VM, iSCSI)', 'none' => 'none (Swap)', }, 'secondarycache' => { 'all' => 'all (general filesystems)', 'metadata' => 'metadata (VM, databases)', 'none' => 'none (media)', }, ); print &ui_form_start( "zvol_create.cgi", "post", undef, "onsubmit='return validateZvolForm(this)'" ); print &ui_hidden( "device", $in{'device'} ); print &ui_hidden( "slice", $in{'slice'} ); print &ui_hidden( "part", $in{'part'} ); print &ui_hidden( "parent", $parent ); print &ui_table_start( $text{'newfs_zvol_header'}, 'width=100%', 2 ); print &ui_table_row( $text{'newfs_zvol_name'}, html_escape($parent) . "/" . &ui_textbox( "zvol", undef, 24 ) ); print &ui_table_row( $text{'newfs_zvol_size'}, &ui_textbox( 'size', undef, 20, undef, undef, "oninput='updateRefres()'" ) ); print &ui_table_end(); print &ui_table_start( $text{'newfs_zvol_opts'}, "width=100%", undef ); foreach my $key ( qw(volblocksize compression sync logbias primarycache secondarycache)) { my @options; push( @options, [ 'default', 'default' ] ); foreach my $opt ( split( ", ", $zvol_opts{$key} ) ) { my $label = $zvol_desc{$key}{$opt} || $opt; push( @options, [ $opt, $label ] ); } my $default_val = $zvol_defaults{$key} || 'default'; my $selected = defined( $in{$key} ) ? $in{$key} : $default_val; print ui_table_row( $key . ': ', ui_select( $key, $selected, \@options, 1, 0, 1 ) ); } my $sparse_default = defined( $in{'sparse'} ) ? $in{'sparse'} : 1; print ui_table_row( $text{'newfs_zvol_sparse'}, ui_yesno_radio( 'sparse', $sparse_default ) . " " . $text{'newfs_zvol_sparse_desc'} . "" ); print ui_table_row( $text{'newfs_zvol_refreservation'}, ui_textbox( 'refreservation', 'none', 20 ) . "" ); print &ui_table_end(); print &ui_form_end( [ [ undef, $text{'create'} ] ] ); print &ui_tabs_end_tab( "mode", "zvol" ); print &ui_tabs_end(1); print <<'EOF'; EOF if ( $in{'part'} ne '' ) { &ui_print_footer( "edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part", $text{'part_return'} ); } else { &ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice", $text{'slice_return'} ); } exit; } # Default: UFS newfs form &ui_print_header( $object->{'desc'}, $text{'newfs_title'}, "" ); my $confirm_msg = $text{'confirm_overwrite'} || 'You will destroy/overwrite existing data structures. Continue?'; my $confirm_js = $confirm_msg; $confirm_js =~ s/\\/\\\\/g; $confirm_js =~ s/'/\\'/g; $confirm_js =~ s/\r?\n/\\n/g; print &ui_form_start( "newfs.cgi", "post", undef, "onsubmit=\"return confirm('$confirm_js')\"" ); print &ui_hidden( "device", $in{'device'} ); print &ui_hidden( "slice", $in{'slice'} ); print &ui_hidden( "part", $in{'part'} ); print &ui_table_start( $text{'newfs_header'}, undef, 2 ); print &ui_table_row( $text{'part_device'}, "$object->{'device'}" ); print &ui_table_row( $text{'newfs_free'}, &ui_opt_textbox( "free", undef, 4, $text{'newfs_deffree'} ) . "%" ); print &ui_table_row( $text{'newfs_trim'}, &ui_yesno_radio( "trim", 0 ) ); print &ui_table_row( $text{'newfs_label'}, &ui_opt_textbox( "label", undef, 20, $text{'newfs_none'} ) ); print &ui_table_end(); print &ui_form_end( [ [ undef, $text{'save'} ] ] ); if ( $in{'part'} ne '' ) { &ui_print_footer( "edit_part.cgi?device=$url_device&slice=$url_slice&part=$url_part", $text{'part_return'} ); } else { &ui_print_footer( "edit_slice.cgi?device=$url_device&slice=$url_slice", $text{'slice_return'} ); }