package diskdrake; # $Id: diskdrake.pm,v 1.225 2001/12/05 23:05:17 prigaux Exp $





use common;
use resize_fat::main;
use my_gtk qw(:helpers :wrappers :ask);
use partition_table qw(:types);
use partition_table_raw;
use detect_devices;
use diskdrake_interactive;
use run_program;
use loopback;
use devices;
use network::smb;
use network::nfs;
use raid;
use any;
use log;
use fsedit;
use fs;

my ($width, $height, $minwidth) = (400, 50, 5);
my ($all_hds, $in, $current_kind, $current_entry, $update_all);
my ($w, @notebook, $done_button);

=begin

struct {
  string name      # which is displayed in tab of the notebook
  bool no_auto     # wether the kind can disappear after creation
  string type      # one of { 'hd', 'raid', 'lvm', 'loopback', 'removable', 'nfs', 'smb' }
  hd | hd_lvm | part_raid[] | part_loopback[] | raw_hd[]  val

  # 
  widget main_box
  widget display_box
  widget action_box
  widget info_box
} current_kind

part current_entry

notebook current_kind[]

=cut

sub main {
    ($in, $all_hds, my $nowizard) = @_;

    @notebook = ();

    local $in->{grab} = 1;

    $w = my_gtk->new('DiskDrake');
    my $rc = "/etc/gtk/diskdrake.rc";
    -r $rc or $rc = dirname(__FILE__) . "/diskdrake.rc";
    -r $rc or $rc = dirname(__FILE__) . "/share/diskdrake.rc";
    Gtk::Rc->parse($rc);

    # TODO
#    is_empty_array_ref($all_hds->{raids}) or raid::stopAll;
#    updateLoopback();

    gtkadd($w->{window},
	   gtkpack_(new Gtk::VBox(0,7),
		    0, (my $filesystems_button_box = filesystems_button_box()),
		    1, (my $notebook_widget = new Gtk::Notebook),
		    0, (my $per_kind_action_box = new Gtk::HBox(0,0)),
		    0, (my $general_action_box  = new Gtk::HBox(0,0)),
		   ),
	  );
    my $lock;
    $update_all = sub {
	$lock and return;
	$lock = 1;
	partition_table::assign_device_numbers($_) foreach fsedit::all_hds($all_hds);
	create_automatic_notebooks($notebook_widget);
	general_action_box($general_action_box, $nowizard);
	per_kind_action_box($per_kind_action_box, $current_kind);
	$current_kind->{type} =~ /hd|lvm|raid|loopback/ ? $filesystems_button_box->show : $filesystems_button_box->hide;
	current_kind_changed($in, $current_kind);
	current_entry_changed($current_kind, $current_entry);
	$lock = 0;
    };
    create_automatic_notebooks($notebook_widget);
    create_static_notebooks($notebook_widget);

    $notebook_widget->signal_connect('switch_page' => sub {
	$current_kind = $notebook[$_[2]];
	$current_entry = '';
	$update_all->();
    });
    $w->sync;
    $done_button->grab_focus;
    $my_gtk::pop_it = 1;
    $in->ask_okcancel(_("Read carefully!"), _("Please make a backup of your data first"), 1) or return
      if $::isStandalone;
    $in->ask_warn('', 
_("If you plan to use aboot, be carefull to leave a free space (2048 sectors is enough)
at the beginning of the disk")) if (arch() eq 'alpha') and !$::isEmbedded;

    $w->main;
}

sub try {
    my ($name, @args) = @_;
    my $f = $diskdrake_interactive::{$name} or die "unknown function $name";
    try_($name, \&{$f}, @args);
}
sub try_ {
    my ($name, $f, @args) = @_;

    fsedit::undo_prepare($all_hds) if $name ne 'Undo';

    my $v = eval { $f->($in, @args, $all_hds); };
    if (my $err = $@) {
	$err =~ /setstep/ and die '';
    	$in->ask_warn(_("Error"), common::formatError($err));
    }

    if ($current_kind->{type} =~ /hd|lvm|raid|loopback/) {
	$current_entry = '' if !diskdrake_interactive::is_part_existing($current_entry, $all_hds);
    }
    $update_all->();

    if ($v && member($name, 'Done', 'Wizard')) {
	$::isEmbedded ? kill( 'USR1', $::CCPID) : Gtk->main_quit; 
    }
}

################################################################################
# generic: helpers
################################################################################
sub add_kind2notebook {
    my ($notebook_widget, $kind) = @_;
    die if $kind->{main_box};

    $kind->{display_box} = gtkset_usize(new Gtk::HBox(0,0), $width, $height);
    $kind->{action_box} = gtkset_usize(new Gtk::VBox(0,0), 130, 150);
    $kind->{info_box} = new Gtk::VBox(0,0);
    $kind->{main_box} =
      gtkpack_(new Gtk::VBox(0,7),
	       0, $kind->{display_box},
	       1, gtkpack_(new Gtk::HBox(0,7),
			   0, $kind->{action_box},
			   1, $kind->{info_box}));
    my_gtk::add2notebook($notebook_widget, $kind->{name}, $kind->{main_box});
    push @notebook, $kind;
    $kind;
}

sub general_action_box {
    my ($box, $nowizard) = @_;
    $_->widget->destroy foreach $box->children;
    my @actions = (if_($::isInstall && !$nowizard, __("Wizard")), 
		   diskdrake_interactive::general_possible_actions($in, $all_hds), 
		   __("Done"));
    foreach my $s (@actions) {
	my $button = new Gtk::Button(translate($s));
	$done_button = $button if $s eq 'Done';
	gtkadd($box, gtksignal_connect($button, clicked => sub { try($s) }));
    }
}
sub per_kind_action_box {
    my ($box, $kind) = @_;
    $_->widget->destroy foreach $box->children;
    $kind or return;
    if ($kind->{type} =~ /hd|lvm|raid|loopback/) {
	foreach my $s (diskdrake_interactive::hd_possible_actions($in, kind2hd($kind), $all_hds)) {
	    gtkadd($box, 
		   gtksignal_connect(new Gtk::Button(translate($s)),
				     clicked => sub { try($s, kind2hd($kind)) }));
	}
    }

}
sub per_entry_action_box {
    my ($box, $kind, $entry) = @_;
    $_->widget->destroy foreach $box->children;


    if ($kind->{type} =~ /nfs|smb/) {
	my @buttons;
	
	push @buttons,
	  map {
	      my $s = $_;
	      gtksignal_connect(new Gtk::Button(translate($s)), clicked => sub { try($s, {}, $entry) });
	  } (if_($entry->{isMounted}, __("Unmount")),
	     if_($entry->{mntpoint} && !$entry->{isMounted}, __("Mount"))) if $entry;

	my %l = (
		 if_($entry, _("Mount point") => \&raw_hd_mount_point),
		 if_($entry && $entry->{mntpoint}, _("Options") => \&raw_hd_options),
		 _("Export") => \&any::fileshare_config,
		);
	push @buttons, map_each {
	    my ($txt, $f) = @_;
	    gtksignal_connect(new Gtk::Button($txt), clicked => sub { try_('', $f, $entry) });
	} %l;

	gtkadd($box, gtkpack(new Gtk::HBox(0,0), @buttons));
    } elsif ($entry) {
	my @buttons;

	if ($kind->{type} =~ /hd|lvm|raid|loopback/) {
	    @buttons = map { 
		my $s = $_;
		my $w = new Gtk::Button(translate($s));
		$w->signal_connect(clicked => sub { try($s, kind2hd($kind), $entry) });
		$w;
	    } diskdrake_interactive::part_possible_actions($in, kind2hd($kind), $entry, $all_hds);
	} elsif ($kind->{type} =~ /removable/) {
	    my %l = (
		     _("Mount point") => \&raw_hd_mount_point,
		     _("Options") => \&raw_hd_options,
		     _("Type") => \&removable_type,
		    );
	    @buttons = map_each {
		my ($txt, $f) = @_;
		gtksignal_connect(new Gtk::Button($txt), clicked => sub { try_('', $f, $entry) });
	    } %l;
	}

	gtkadd($box, gtkadd(new Gtk::Frame(_("Choose action")),
			    createScrolledWindow(gtkpack__(new Gtk::VBox(0,0), @buttons)))) if @buttons;

    } else {
	my $txt;
	if ($kind->{type} =~ /hd|lvm|raid|loopback/) {
	    $txt = !$::isStandalone && fsedit::is_one_big_fat($all_hds->{hds}) ?
_("You have one big FAT partition
(generally used by MicroSoft Dos/Windows).
I suggest you first resize that partition
(click on it, then click on \"Resize\")") : _("Please click on a partition");
        } elsif ($kind->{type} =~ /removable/) {
            $txt = _("Please click on a media");
        }
	gtkpack($box, gtktext_insert(new Gtk::Text, $txt));
    }
}

sub per_entry_info_box {
    my ($box, $kind, $entry) = @_;
    $_->widget->destroy foreach $box->children;
    my $info;
    if ($kind->{type} =~ /hd|lvm|raid|loopback/) {
	if ($entry) {
	    $info = diskdrake_interactive::format_part_info(kind2hd($kind), $entry);
	} elsif ($kind->{type} =~ /hd|lvm/) {
	    $info = diskdrake_interactive::format_hd_info($kind->{val});
	}
    } elsif ($kind->{type} =~ /removable|nfs|smb/ && $entry) {
	$info = diskdrake_interactive::format_raw_hd_info($entry);
    }
    gtkpack($box, gtkadd(new Gtk::Frame(_("Details")), gtkset_justify(new Gtk::Label($info), 'left')));
}

sub current_kind_changed {
    my ($in, $kind) = @_;

    if ($kind->{type} !~ /nfs|smb/) { 
	$_->widget->destroy foreach $kind->{display_box}->children;
    }

    if ($kind->{type} =~ /hd|lvm|raid|loopback/) {
	my $v = $kind->{val};
	my @parts = 
	  $kind->{type} eq 'raid' ? grep {$_} @$v :
	  $kind->{type} eq 'loopback' ? @$v : fsedit::get_fstab_and_holes($v);
	my $totalsectors = 
	  $kind->{type} =~ /raid|loopback/ ? sum(map { $_->{size} } @parts) : $v->{totalsectors};
	create_buttons4partitions($kind, $totalsectors, @parts);
    } elsif ($kind->{type} =~ /removable/) {	
	create_buttons4raw_hd($kind, $kind->{val});
    } elsif ($kind->{type} eq 'nfs') {
	network::nfs::check($in);
    } elsif ($kind->{type} eq 'smb') {
	network::smb::check($in);
    }
}

sub current_entry_changed {
    my ($kind, $entry) = @_;
    $current_entry = $entry;
    if ($kind) {
	per_entry_action_box($kind->{action_box}, $kind, $entry);
	per_entry_info_box($kind->{info_box}, $kind, $entry);
    }    
}

sub create_static_notebooks {
    my ($notebook_widget) = @_;

    if ($::isStandalone) {
	add_kind2notebook($notebook_widget, removable2kind($all_hds->{raw_hds})) if @{$all_hds->{raw_hds}};

	{
	    my $find_servers = sub {
		my $w = $in->wait_message('', _("Scanning available nfs shared resource"));
		&network::nfs::find_servers;
	    };
	    my $find_exports = sub {
		my ($server) = @_;
		my $w = $in->wait_message('', _("Scanning available nfs shared resource of server %s", $server->{name}));
		&network::nfs::find_exports;
	    };
	    add_smbnfs2notebook($notebook_widget, nfs2kind($all_hds->{nfss}), $find_servers, $find_exports, \&nfs_create);
	}
	{
	    my $find_servers = sub {
		my $w = $in->wait_message('', _("Scanning available samba shared resource"));
		&network::smb::find_servers;
	    };
	    my $find_exports = sub {
		my ($server) = @_;
		my $w = $in->wait_message('', _("Scanning available samba shared resource of server %s", $server->{name}));
		&network::smb::find_exports;
	    };
	    add_smbnfs2notebook($notebook_widget, smb2kind($all_hds->{smbs}), $find_servers, $find_exports, \&smb_create);
	}
    }
}

sub create_automatic_notebooks {
    my ($notebook_widget) = @_;
    my @l = fsedit::all_hds($all_hds);

    $_->{marked} = 0 foreach @notebook;
    my $may_add = sub {
	my ($kind) = @_;
	my @l = grep { $kind->{val} == $_->{val} } @notebook;
	@l > 1 and log::l("weird: create_automatic_notebooks");
	$kind = $l[0] || add_kind2notebook($notebook_widget, $kind);
	$kind->{marked} = 1;
    };
    $may_add->(hd2kind($_)) foreach @{$all_hds->{hds}};
    $may_add->(lvm2kind($_)) foreach @{$all_hds->{lvms}};
    $may_add->(raid2kind()) if grep {$_} @{$all_hds->{raids}};
    $may_add->(loopback2kind()) if @{$all_hds->{loopbacks}};

    @notebook = grep_index {
	my $b = $_->{no_auto} || $_->{marked} or $notebook_widget->remove_page($::i);
	$b;
    } @notebook;
    @notebook or die '';
}

################################################################################
# parts: helpers
################################################################################

sub create_buttons4partitions {
    my ($kind, $totalsectors, @parts) = @_;

    $width = max($width, 0.9 * second($w->{window}->window->get_size)) if $w->{window}->window;

    my $ratio = $totalsectors ? ($width - @parts * $minwidth) / $totalsectors : 1;
    while (1) {
	my $totalwidth = sum(map { $_->{size} * $ratio + $minwidth } @parts);
	$totalwidth <= $width and last;
	$ratio /= $totalwidth / $width * 1.1;
    }

    foreach my $entry (@parts) {
	my $w = new Gtk::Button($entry->{mntpoint} || '') or die '';
	$w->signal_connect(focus_in_event     => sub { current_entry_changed($kind, $entry) });
	$w->signal_connect(button_press_event => sub { current_entry_changed($kind, $entry) });
	$w->signal_connect(key_press_event => sub {
	    my ($w, $e) = @_;
	    $e->{state} & 4 or return; 
	    my $c = chr $e->{keyval};

	    foreach my $s (diskdrake_interactive::part_possible_actions($in, kind2hd($kind), $entry, $all_hds)) {
		${{
		    Create => 'c', Delete => 'd', Format => 'f', 
		    Loopback => 'l', Resize => 'r', Type => 't', 
		    Mount => 'M', Unmount => 'u', 'Mount point' => 'm',
		    'Add to LVM' => 'L', 'Remove from LVM' => 'L', 
		    'Add to RAID' => 'R', 'Remove from RAID' => 'R',
		}}{$s} eq $c or next;

		try($s, kind2hd($kind), $entry);
		last;
	    }
	});
	$w->set_name("PART_" . type2name($entry->{type}));
	$w->set_usize($entry->{size} * $ratio + $minwidth, 0);
	gtkpack__($kind->{display_box}, $w);
	$w->grab_focus if $current_entry && fsedit::is_same_part($current_entry, $entry);
    }
}


################################################################################
# disks: helpers
################################################################################
sub current_hd { 
    $current_kind->{type} eq 'hd' or die 'current_hd called but $current_kind is not an hd';
    $current_kind->{val};
}
sub current_part {
    current_hd();
    $current_entry;
}

sub kind2hd {
    my ($kind) = @_;
    $kind->{type} =~ /hd|lvm/ ? $kind->{val} : {}
}

sub hd2kind {
    my ($hd) = @_;
    { type => 'hd', name => $hd->{device}, val => $hd };
}

sub filesystems_button_box() {
    my @types = (__("Ext2"), __("Journalised FS"), __("Swap"), arch() =~ /sparc/ ? __("SunOS") : arch() eq "ppc" ? __("HFS") : __("FAT"),
		 __("Other"), __("Empty"));
    my %name2type = (Ext2 => 0x83, 'Journalised FS' => 0x483, Swap => 0x82, Other => 1, FAT => 0xb, HFS => 0x402);

    gtkpack(new Gtk::HBox(0,0), 
	    _("Filesystem types:"),
	    map { my $w = new Gtk::Button(translate($_));
		  my $t = $name2type{$_};
		  $w->signal_connect(clicked => sub { try_('', \&createOrChangeType, $t, current_hd(), current_part()) });
		  $w->can_focus(0);
		  $w->set_name($_); 
		  $w;
	    } @types);
}

sub createOrChangeType {
    my ($in, $type, $hd, $part, $all_hds) = @_;

    $part ||= !fsedit::get_fstab($hd) && 
              { type => 0, start => 1, size => $hd->{totalsectors} - 1 };
    $part or return;
    if ($type == 1) {
	$in->ask_warn('', _("Use ``%s'' instead", $part->{type} ? _("Type") : _("Create")));
    } elsif (!$type) {
	$in->ask_warn('', _("Use ``%s'' instead", _("Delete"))) if $part->{type};
    } elsif ($part->{type}) {
	return unless $::expert;
	return if $type == $part->{type};
	isBusy($part) and $in->ask_warn('', _("Use ``Unmount'' first")), return;
	diskdrake_interactive::ask_alldatawillbelost($in, $part, __("After changing type of partition %s, all data on this partition will be lost")) or return;
	fsedit::change_type($type, $hd, $part);
    } else {
	$part->{type} = $type;
	diskdrake_interactive::Create($in, $hd, $part, $all_hds);
    }
}

################################################################################
# lvms: helpers
################################################################################
sub lvm2kind {
    my ($lvm) = @_;
    { type => 'lvm', name => $lvm->{LVMname}, val => $lvm };
}

################################################################################
# raids: helpers
################################################################################
sub raid2kind {
    { type => 'raid', name => 'raid', val => $all_hds->{raids} };
}


################################################################################
# loopbacks: helpers
################################################################################
sub loopback2kind {
    { type => 'loopback', name => 'loopback', val => $all_hds->{loopbacks} };
}


################################################################################
# removable/nfs/smb: helpers
################################################################################
sub create_buttons4raw_hd {
    my ($kind, $l) = @_;

    foreach my $entry (@$l) {
	my $w = new Gtk::Button($entry->{device}) or die '';
	$w->signal_connect(focus_in_event     => sub { current_entry_changed($kind, $entry) });
	$w->signal_connect(button_press_event => sub { current_entry_changed($kind, $entry) });
	$w->set_name("PART_raw_Linux");
	gtkpack__($kind->{display_box}, $w);
	$w->grab_focus if $current_entry && fsedit::is_same_hd($current_entry, $entry);
    }
}

sub raw_hd_mount_point {
    my ($in, $raw_hd) = @_;

    my $mntpoint = $raw_hd->{mntpoint};
    $in->ask_from(
        '',
        _("Where do you want to mount device %s?", $raw_hd->{device}),
	[ { label => _("Mount point"), val => \$mntpoint, 
	    list => [ if_($mntpoint, $mntpoint), '' ], 
	    not_edit => 0 } ],
	complete => sub {
	    $raw_hd->{mntpoint} eq $mntpoint || diskdrake_interactive::check_mntpoint($in, $mntpoint, {}, $raw_hd, $all_hds) or return 1;
	    0;
	}
    ) or return;
    $raw_hd->{mntpoint} = $mntpoint;
}

sub raw_hd_options {
    my ($in, $raw_hd) = @_;

    my @simple_options = qw(user noauto supermount);

    my (undef, $user_implies) = fs::mount_options();
    my ($options, $unknown) = fs::mount_options_unpack($raw_hd);
    my %help = fs::mount_options_help(keys %$options);

    my $prev_user = $options->{user};
    $in->ask_from(_("Mount options"),
		  '',
		  [ 
		   (map {; 
			 { label => $_, text => formatAlaTeX($help{$_}), val => \$options->{$_}, 
			   advanced => !member($_, @simple_options), if_(!/=$/, type => 'bool'), }
		     } keys %$options),
		    { label => _("Various"), val => \$unknown, advanced => 1 },
		  ],
		  changed => sub {
		      if ($prev_user != $options->{user}) {
			  $prev_user = $options->{user};
			  $options->{$_} = $options->{user} foreach @$user_implies;
		      }
		  },
		 ) or return;

    fs::mount_options_pack($raw_hd, $options, $unknown);
}

################################################################################
# removable: helpers
################################################################################
sub removable2kind {
    my ($medias) = @_;
    { type => 'removable', name => _("Removable media"), val => $medias, no_auto => 1 };
}

sub removable_type {
    my ($in, $raw_hd) = @_;
    my @fs = ('auto', fs::auto_fs());
    my $type = $raw_hd->{type};
    $in->ask_from(_("Change type"),
			      _("Which filesystem do you want?"),
			      [ { label => _("Type"), val => \$type, list => [@fs], not_edit => !$::expert } ]) or return;
    $raw_hd->{type} = $type;
}


sub create_ctree {
    my ($e, $roots) = @_;

    my $tree = Gtk::CTree->new(1, 0);
    my (%wtree, %wleaves, $size, $selected_via_click);

    foreach (@$roots) {
	my $root = $tree->insert_node(undef, undef, [$_->{name}], 5, (undef) x 4, 0, 0);
	my %nodes = map {
	    $_, $tree->insert_node($root, undef, [$_], 5, (undef) x 4, 1, 0);
	} @{$_->{children}};
#	$_->{} foreach @{$_->{}};
    }

    my $select = sub {
	my ($node) = @_;
	for (my $c = $node; $c; $c = $c->row->parent) { 
	    $tree->expand($c);
	}
    };

    my $curr = $tree->node_nth(0); 
    $tree->set_column_auto_resize(0, 1);
    $tree->set_selection_mode('browse');
    $tree->set_row_height($tree->style->font->ascent + $tree->style->font->descent + 1);

    $tree;
}


################################################################################
# nfs/smb: helpers
################################################################################
sub import_ctree {
    my ($kind, $imported, $find_servers, $find_exports, $create) = @_;
    my (%name2server, %wservers, %name2export, $inside);

    my $tree = Gtk::CTree->new(1, 0);
    $tree->set_column_auto_resize(0, 1);
    $tree->set_selection_mode('browse');
    $tree->set_row_height($tree->style->font->ascent + $tree->style->font->descent + 1);

    my $add_server = sub {
	my ($server) = @_;
	my $name = $server->{name} || $server->{ip};
	$name2server{$name} = $server;
	$wservers{$name} ||= $tree->insert_node(undef, undef, [$name], 5, (undef) x 4, 0, 0);
	$wservers{$name}
    };

    my $add_exports = sub {
	my ($node) = @_;
	$tree->expand($node);
	my $name = first $tree->node_get_pixtext($node, 0);
	foreach ($find_exports->($name2server{$name})) {
	    my $name = $_->{name} . ($_->{comment} ? " ($_->{comment})" : '');
	    $name2export{$name} = $_;
	    $tree->insert_node($node, undef, [$name], 5, (undef) x 4, 1, 0);
	}
    };

    my $click_here = $tree->insert_node(undef, undef, [_("click here")], 5, (undef) x 4, 0, 0);
    foreach (@$imported) {
	my $node = $add_server->($_->{server});
	$add_exports->($node);
    }

    $tree->signal_connect(tree_select_row => sub { 
	my $curr = $_[1];
	$inside and return;
	$inside = 1;
	if ($curr->row->is_leaf) {
	    my ($export) = $tree->node_get_pixtext($curr, 0);
	    $export =~ s/ \(.*?\)$//;
	    my ($server) = $tree->node_get_pixtext($curr->row->parent, 0);
	    my $entry = $create->($server, $export);
	    if (my ($e) = grep { $entry->{device} eq $_->{device} } @{$kind->{val}}) {
		$entry = $e;
	    } else {
		push @{$kind->{val}}, $entry;
	    }
	    current_entry_changed($kind, $entry);
	} elsif (!$curr->row->children) {
	    $tree->freeze;
	    if ($click_here == $curr) {
		$add_server->($_) foreach sort { $a->{name} cmp $b->{name} } $find_servers->();
		$tree->remove_node($click_here);
	    } else {
		$add_exports->($curr);
	    }
	    $tree->thaw;
	}
	$inside = 0;
    });
    $tree;
}

sub add_smbnfs2notebook {
    my ($notebook_widget, $kind, $find_servers, $find_exports, $create) = @_;
    die if $kind->{main_box};

    my $imported = [];

    $kind->{display_box} = createScrolledWindow(import_ctree($kind, $imported, $find_servers, $find_exports, $create));
    $kind->{action_box} = new Gtk::HBox(0,0);
    $kind->{info_box} = new Gtk::VBox(0,0);
    $kind->{main_box} =
      gtkpack_(new Gtk::VBox(0,7),
	       1, gtkpack(new Gtk::HBox(0,7),
			  gtkset_usize($kind->{display_box}, 200, 0),
			  $kind->{info_box}),
	       0, $kind->{action_box},
	     );

    my_gtk::add2notebook($notebook_widget, $kind->{name}, $kind->{main_box});
    push @notebook, $kind;
    $kind;
}

################################################################################
# nfs: helpers
################################################################################
sub nfs2kind {
    my ($l) = @_;
    { type => 'nfs', name => 'NFS', val => $l, no_auto => 1 };
}

sub nfs_create {
    my ($server, $export) = @_;

    my $nfs = { device => "$server:$export", type => 'nfs' };
    fs::set_default_options($nfs);
    $nfs;
}

################################################################################
# smb: helpers
################################################################################
sub smb2kind {
    my ($l) = @_;
    { type => 'smb', name => 'Samba', val => $l, no_auto => 1 };
}

sub smb_create {
    my ($server, $export) = @_;

    my $smb = { device => "//$server/$export", type => 'smbfs', options => 'username=%' };
    fs::set_default_options($smb);
    $smb;
}


1;
