summaryrefslogtreecommitdiffstats
path: root/perl-install/diskdrake
diff options
context:
space:
mode:
authorPascal Rigaux <pixel@mandriva.com>2002-01-27 19:33:00 +0000
committerPascal Rigaux <pixel@mandriva.com>2002-01-27 19:33:00 +0000
commitae427550a1ab5528bd033d4b7cc2ed738a5c776a (patch)
tree69e9b6f2d702dce1b8e9d9f0b4bdafa54d8bc5ca /perl-install/diskdrake
parent258b4c29d59ce9d701a46c98f663c2ea961288ba (diff)
downloaddrakx-ae427550a1ab5528bd033d4b7cc2ed738a5c776a.tar
drakx-ae427550a1ab5528bd033d4b7cc2ed738a5c776a.tar.gz
drakx-ae427550a1ab5528bd033d4b7cc2ed738a5c776a.tar.bz2
drakx-ae427550a1ab5528bd033d4b7cc2ed738a5c776a.tar.xz
drakx-ae427550a1ab5528bd033d4b7cc2ed738a5c776a.zip
new diskdrake modules (diskdrake_interactive is now diskdrake::interactive, diskdrake is now diskdrake::hd_gtk, others created from diskdrake.pm)
Diffstat (limited to 'perl-install/diskdrake')
-rw-r--r--perl-install/diskdrake/hd_gtk.pm379
-rw-r--r--perl-install/diskdrake/interactive.pm1112
-rw-r--r--perl-install/diskdrake/removable.pm55
-rw-r--r--perl-install/diskdrake/removable_gtk.pm31
-rw-r--r--perl-install/diskdrake/smbnfs_gtk.pm247
5 files changed, 1824 insertions, 0 deletions
diff --git a/perl-install/diskdrake/hd_gtk.pm b/perl-install/diskdrake/hd_gtk.pm
new file mode 100644
index 000000000..6b6bd76bb
--- /dev/null
+++ b/perl-install/diskdrake/hd_gtk.pm
@@ -0,0 +1,379 @@
+package diskdrake::hd_gtk; # $Id$
+
+use diagnostics;
+use strict;
+
+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 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_changed($in, $current_kind);
+ current_entry_changed($current_kind, $current_entry);
+ $lock = 0;
+ };
+ create_automatic_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"), formatError($err));
+ }
+
+ $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), 150, 180);
+ $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;
+ 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 ($entry) {
+ my @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);
+
+ gtkadd($box, gtkadd(new Gtk::Frame(_("Choose action")),
+ createScrolledWindow(gtkpack__(new Gtk::VBox(0,0), @buttons)))) if @buttons;
+ } else {
+ my $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");
+ 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 ($entry) {
+ $info = diskdrake::interactive::format_part_info(kind2hd($kind), $entry);
+ } elsif ($kind->{type} =~ /hd|lvm/) {
+ $info = diskdrake::interactive::format_hd_info($kind->{val});
+ }
+ gtkpack($box, gtkadd(new Gtk::Frame(_("Details")), gtkset_justify(new Gtk::Label($info), 'left')));
+}
+
+sub current_kind_changed {
+ my ($in, $kind) = @_;
+
+ $_->widget->destroy foreach $kind->{display_box}->children;
+
+ 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);
+}
+
+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_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 = $_->{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} };
+}
+
+1;
diff --git a/perl-install/diskdrake/interactive.pm b/perl-install/diskdrake/interactive.pm
new file mode 100644
index 000000000..2a86d73af
--- /dev/null
+++ b/perl-install/diskdrake/interactive.pm
@@ -0,0 +1,1112 @@
+package diskdrake::interactive; # $Id$
+
+use diagnostics;
+use strict;
+
+use common;
+use partition_table qw(:types);
+use partition_table_raw;
+use detect_devices;
+use run_program;
+use loopback;
+use devices;
+use fsedit;
+use raid;
+use any;
+use log;
+use fs;
+
+
+=begin
+
+struct part {
+ int active # one of { 0 | 0x80 } x86 only, primary only
+ int start # in sectors
+ int size # in sectors
+ int type # 0x82, 0x83, 0x6 ...
+ string device # 'hda5', 'sdc1' ...
+ string rootDevice # 'sda', 'hdc' ...
+ string real_mntpoint # directly on real /, '/tmp/hdimage' ...
+ string mntpoint # '/', '/usr' ...
+ string options # 'defaults', 'noauto'
+ string device_windobe # 'C', 'D' ...
+ string encrypt_key # [0-9A-Za-z./]{20,}
+
+ bool isMounted
+
+ bool isFormatted
+ bool notFormatted
+ # isFormatted means the device is formatted
+ # !isFormatted && notFormatted means the device is not formatted
+ # !isFormatted && !notFormatted means we don't know which state we're in
+
+ int raid # for partitions of type isRawRAID and which isPartOfRAID, the raid device number
+ string lvm # partition used as a PV for the VG with {lvm} as LVMname
+ loopback loopback[] # loopback living on this partition
+
+ # internal
+ string real_device # '/dev/loop0', '/dev/loop1' ...
+
+ # internal CHS (Cylinder/Head/Sector)
+ int start_cyl, start_head, start_sec, end_cyl, end_head, end_sec,
+}
+
+struct part_allocate inherits part {
+ int maxsize # in sectors (alike "size")
+ int ratio #
+ string hd # 'hda', 'hdc'
+ string parts # for creating raid partitions. eg: 'foo bar' where 'foo' and 'bar' are mntpoint
+}
+
+struct part_raid inherits part {
+ string chunk-size # usually '64k'
+ string level # one of { 0, 1, 4, 5, 'linear' }
+
+ part disks[]
+
+ # invalid: active, start, rootDevice, device_windobe?, CHS
+}
+
+struct part_loopback inherits part {
+ string loopback_file # absolute file name which is relative to the partition
+ part loopback_device # where the loopback file live
+
+ # device is special here: it is the absolute filename of the loopback file.
+
+ # invalid: active, start, rootDevice, device_windobe, CHS
+}
+
+struct part_lvm inherits part {
+ # invalid: active, start, device_windobe, CHS
+}
+
+
+struct partition_table_elem {
+ part normal[] #
+ part extended # the main/next extended
+ part raw[4] # primary partitions
+}
+
+struct geom {
+ int heads
+ int sectors
+ int cylinders
+ int totalcylinders # for SUN, forget it
+ int start # always 0, forget it
+}
+
+struct hd {
+ int totalsectors # size in sectors
+ string device # 'hda', 'sdc' ...
+ string device_alias # 'cdrom', 'floppy' ...
+ string media_type # one of { 'hd', 'cdrom', 'fd', 'tape' }
+ string info # name of the hd, eg: 'QUANTUM ATLAS IV 9 WLS'
+
+ bool isDirty # does it need to be written to the disk
+ bool needKernelReread # must we tell the kernel to reread the partition table
+ bool hasBeenDirty # for undo
+ bool rebootNeeded # happens when a kernel reread failed
+ int bus, id
+
+ partition_table_elem primary
+ partition_table_elem extended[]
+
+ geom geom
+
+ # internal
+ string prefix # for some RAID arrays device=>c0d0 and prefix=>c0d0p
+ string file # '/dev/hda' ...
+}
+
+struct hd_lvm inherits hd {
+ int PE_size # block size (granularity, similar to cylinder size on x86)
+ string LVMname # VG name
+
+ part_lvm disks[]
+
+ # invalid: bus, id, extended, geom
+}
+
+struct raw_hd inherits hd {
+ string type # 0x82, 0x83, 'nfs', ...
+ string mntpoint # '/', '/usr' ...
+ string options # 'defaults', 'noauto'
+
+ # invalid: isDirty, needKernelReread, hasBeenDirty, rebootNeeded, primary, extended
+}
+
+struct all_hds {
+ hd hds[]
+ hd_lvm lvms[]
+ part_raid raids[] # indexed by number: raids[$n]{device} is "md$n"
+ part_loopback loopbacks[]
+ raw_hd raw_hds[]
+ raw_hd nfss[]
+ raw_hd smbs[]
+ raw_hd special[]
+
+ # internal: if fstab_to_string($all_hds) eq current_fstab then no need to save
+ string current_fstab
+}
+
+
+=cut
+
+
+sub main {
+ my ($in, $all_hds) = @_;
+
+ if ($in->isa('interactive_gtk')) {
+ require diskdrake::hd_gtk;
+ goto &diskdrake::hd_gtk::main;
+ }
+
+ my ($current_part, $current_hd);
+
+ while (1) {
+ my $choose_txt = $current_part ? __("Choose another partition") : __("Choose a partition");
+ my $parts_and_holes = [ fsedit::get_all_fstab_and_holes($all_hds) ];
+ my $choose_part = sub {
+ $current_part = $in->ask_from_listf('diskdrake', translate($choose_txt), sub { format_part_info_short(fsedit::part2hd($_[0], $all_hds), $_[0]) }, $parts_and_holes, $current_part) || return;
+ $current_hd = fsedit::part2hd($current_part, $all_hds);
+ };
+
+ $choose_part->() if !$current_part;
+ return if !$current_part;
+
+ my %actions = my @actions = (
+ if_($current_part,
+ (map { my $s = $_; $_ => sub { $diskdrake::interactive::{$s}($in, $current_hd, $current_part, $all_hds) } } part_possible_actions($in, $current_hd, $current_part, $all_hds)),
+ '____________________________' => sub {},
+ ),
+ if_(@$parts_and_holes > 1, $choose_txt => $choose_part),
+ if_($current_hd,
+ (map { my $s = $_; $_ => sub { $diskdrake::interactive::{$s}($in, $current_hd, $all_hds) } } hd_possible_actions_interactive($in, $current_hd, $all_hds)),
+ ),
+ (map { my $s = $_; $_ => sub { $diskdrake::interactive::{$s}($in, $all_hds) } } general_possible_actions($in, $all_hds)),
+ );
+ my ($actions) = list2kv(@actions);
+ my $a;
+ if ($current_part) {
+ $in->ask_from_({
+ cancel => _("Exit"),
+ title => 'diskdrake',
+ messages => format_part_info($current_hd, $current_part),
+ },
+ [ { val => \$a, list => $actions, type => 'list', sort => 0, gtk => { use_boxradio => 0 } } ]) or last;
+ $actions{$a}();
+ $current_hd = $current_part = '' if !is_part_existing($current_part, $all_hds);
+ } else {
+ $choose_part->();
+ }
+ partition_table::assign_device_numbers($_) foreach fsedit::all_hds($all_hds);
+ }
+ Done($in, $all_hds) or goto &main;
+}
+
+
+
+
+################################################################################
+# general actions
+################################################################################
+sub general_possible_actions {
+ __("Undo"), ($::expert ? __("Toggle to normal mode") : __("Toggle to expert mode"));
+}
+
+
+sub Undo {
+ my ($in, $all_hds) = @_;
+ fsedit::undo($all_hds);
+}
+
+sub Wizard {
+ $::o->{wizard} = 1;
+ goto &Done;
+}
+
+sub Done {
+ my ($in, $all_hds) = @_;
+ eval { raid::verify($all_hds->{raids}) };
+ if ($@) {
+ $::expert or die;
+ $in->ask_okcancel('', [ $@, _("Continue anyway?")]) or return;
+ }
+ foreach (@{$all_hds->{hds}}) {
+ if (!write_partitions($in, $_)) {
+ return if !$::isStandalone;
+ $in->ask_yesorno(_("Quit without saving"), _("Quit without writing the partition table?"), 1) or return;
+ }
+ }
+ if (!$::isInstall) {
+ my $new = fs::fstab_to_string($all_hds);
+ if ($new ne $all_hds->{current_fstab} && $in->ask_yesorno('', _("Do you want to save /etc/fstab modifications"), 1)) {
+ $all_hds->{current_fstab} = $new;
+ fs::write_fstab($all_hds);
+ }
+ }
+ 1;
+}
+
+################################################################################
+# per-hd actions
+################################################################################
+sub hd_possible_actions {
+ __("Clear all"), if_($::isInstall, __("Auto allocate")), __("More");
+}
+sub hd_possible_actions_interactive {
+ hd_possible_actions(), __("Hard drive information");
+}
+
+sub Clear_all {
+ my ($in, $hd, $all_hds) = @_;
+ isPartOfLVM($_) and RemoveFromLVM($in, $hd, $_, $all_hds) foreach partition_table::get_normal_parts($hd);
+ partition_table_raw::zero_MBR_and_dirty($hd);
+}
+
+sub Auto_allocate {
+ my ($in, $hd, $all_hds) = @_;
+ my $suggestions = partitions_suggestions($in) or return;
+
+ my %all_hds_ = %$all_hds;
+ $all_hds_{hds} = [ sort { $a == $hd ? -1 : 1 } @{$all_hds->{hds}} ];
+
+ eval { fsedit::auto_allocate(\%all_hds_, $suggestions) };
+ if ($@) {
+ $@ =~ /partition table already full/ or die;
+
+ $in->ask_warn("", [
+ _("All primary partitions are used"),
+ _("I can't add any more partition"),
+ _("To have more partitions, please delete one to be able to create an extended partition"),
+ ]);
+ }
+}
+
+sub More {
+ my ($in, $hd) = @_;
+
+ $in->ask_from('', '',
+ [
+ { val => _("Save partition table"), clicked_may_quit => sub { SaveInFile($in, $hd); 1 } },
+ { val => _("Restore partition table"), clicked_may_quit => sub { ReadFromFile($in, $hd); 1 } },
+ { val => _("Rescue partition table"), clicked_may_quit => sub { Rescuept($in, $hd); 1 } },
+ if_($::isInstall,
+ { val => _("Reload partition table"), clicked => sub {
+ $::o->{all_hds} = fsedit::empty_all_hds();
+ die "setstep doPartitionDisks\n" if $::setstep;
+ } }),
+ if_($::isInstall,
+ { text => _("Removable media automounting"), val => \$::o->{useSupermount}, type => 'bool' },
+ ),
+ ],
+ );
+}
+
+sub ReadFromFile {
+ my ($in, $hd) = @_;
+
+ my $file = $::isStandalone ? $in->ask_file(_("Select file")) : devices::make("fd0") or return;
+
+ eval {
+ catch_cdie { partition_table::load($hd, $file) }
+ sub {
+ $@ =~ /bad totalsectors/ or return;
+ $in->ask_yesorno('',
+_("The backup partition table has not the same size
+Still continue?"), 0);
+ };
+ };
+ if (my $err = $@) {
+ $in->ask_warn(_("Error"), formatError($err));
+ }
+}
+
+sub SaveInFile {
+ my ($in, $hd) = @_;
+
+ my $file = $::isStandalone ?
+ $in->ask_file(_("Select file")) :
+ $in->ask_okcancel(_("Warning"),
+_("Insert a floppy in drive
+All data on this floppy will be lost"), 1) && devices::make(detect_devices::floppy()) or return;
+
+ eval { partition_table::save($hd, $file) };
+ if (my $err = $@) {
+ $in->ask_warn(_("Error"), formatError($err));
+ }
+}
+
+sub Rescuept {
+ my ($in, $hd) = @_;
+ my $w = $in->wait_message('', _("Trying to rescue partition table"));
+ fsedit::rescuept($hd);
+}
+
+sub Hd_info {
+ my ($in, $hd) = @_;
+ $in->ask_warn('', [ _("Detailed information"), format_hd_info($hd) ]);
+}
+
+################################################################################
+# per-part actions
+################################################################################
+
+sub part_possible_actions {
+ my ($in, $hd, $part, $all_hds) = @_;
+ $part or return;
+
+ my %actions = my @l = (
+ __("Mount point") => '($part->{real_mntpoint} && common::usingRamdisk()) || (!isBusy && !isSwap && !isNonMountable)',
+ __("Type") => '!isBusy && $::expert',
+ __("Options") => '$::expert',
+ __("Resize") => '!isBusy && !isSpecial',
+ __("Move") => '!isBusy && !isSpecial && $::expert && 0', # disable for the moment
+ __("Format") => '!isBusy && ($::expert || $::isStandalone)',
+ __("Mount") => '!isBusy && (hasMntpoint || isSwap) && maybeFormatted && ($::expert || $::isStandalone)',
+ __("Add to RAID") => '!isBusy && isRawRAID && !isSpecial',
+ __("Add to LVM") => '!isBusy && isRawLVM',
+ __("Unmount") => '!$part->{real_mntpoint} && isMounted',
+ __("Delete") => '!isBusy',
+ __("Remove from RAID") => 'isPartOfRAID',
+ __("Remove from LVM") => 'isPartOfLVM',
+ __("Modify RAID") => 'isPartOfRAID && !isMounted($all_hds->{raids}[$part->{raid}])',
+ __("Use for loopback") => '!$part->{real_mntpoint} && isMountableRW && !isSpecial && hasMntpoint && $::expert',
+ );
+ my ($actions_names) = list2kv(@l);
+ my %macros = (
+ hasMntpoint => '$part->{mntpoint}',
+ isPrimary => 'isPrimary($part, $hd)',
+ );
+ if ($part->{type} == 0) {
+ __("Create");
+ } else {
+ grep {
+ my $cond = $actions{$_};
+ while (my ($k, $v) = each %macros) {
+ $cond =~ s/$k/qq(($v))/e;
+ }
+ $cond =~ s/(^|[^:\$]) \b ([a-z]\w{3,}) \b ($|[\s&\)])/$1 . $2 . '($part)' . $3/exg;
+ if (/Create/) {
+ 1;
+ }
+ eval $cond;
+ } @$actions_names;
+ }
+}
+
+sub Create {
+ my ($in, $hd, $part, $all_hds) = @_;
+ my ($def_start, $def_size, $max) = ($part->{start}, $part->{size}, $part->{start} + $part->{size});
+
+ $part->{maxsize} = $part->{size}; $part->{size} = 0;
+ if (!fsedit::suggest_part($part, $all_hds)) {
+ $part->{size} = $part->{maxsize};
+ $part->{type} ||= 0x483;
+ }
+
+ #- update adjustment for start and size, take into account the minimum partition size
+ #- including one less sector for start due to a capacity to increase the adjustement by
+ #- one.
+ my ($primaryOrExtended, $migrate_files);
+ my $type = type2name($part->{type});
+ my $mb_size = $part->{size} >> 11;
+ my $has_startsector = ($::expert || arch() !~ /i.86/) && !isLVM($hd);
+
+ my $w = $in->ask_from(_("Create a new partition"), '',
+ [
+ if_($has_startsector,
+ { label => _("Start sector: "), val => \$part->{start}, min => $def_start, max => ($max - min_partition_size($hd)), type => 'range' },
+ ),
+ { label => _("Size in MB: "), val => \$mb_size, min => min_partition_size($hd) >> 11, max => $def_size >> 11, type => 'range' },
+ { label => _("Filesystem type: "), val => \$type, list => [ partition_table::important_types() ], not_edit => !$::expert, sort => 0 },
+ { label => _("Mount point: "), val => \$part->{mntpoint}, list => [ fsedit::suggestions_mntpoint($all_hds), '' ],
+ disabled => sub { my $p = { type => name2type($type) }; isSwap($p) || isNonMountable($p) }, type => 'combo', not_edit => 0,
+ },
+ if_($::expert && $hd->hasExtended,
+ { label => _("Preference: "), val => \$primaryOrExtended, list => [ '', "Extended", "Primary", if_($::expert, "Extended_0x85") ] },
+ ),
+ ], changed => sub {
+ if ($part->{start} + ($mb_size << 11) > $max) {
+ if ($_[0] == 0) {
+ # Start sector changed => restricting Size
+ $mb_size = ($max - $part->{start}) >> 11;
+ } else {
+ # Size changed => restricting Start sector
+ $part->{start} = $max - ($mb_size << 11);
+ }
+ }
+ }, complete => sub {
+ $part->{size} = from_Mb($mb_size, min_partition_size($hd), $max - $part->{start}); #- need this to be able to get back the approximation of using MB
+ $part->{type} = name2type($type);
+ $part->{mntpoint} = '' if isNonMountable($part);
+ $part->{mntpoint} = 'swap' if isSwap($part);
+ fs::set_default_options($part);
+
+ check($in, $hd, $part, $all_hds) or return 1;
+ $migrate_files = need_migration($in, $part->{mntpoint}) or return 1;
+
+ fsedit::add($hd, $part, $all_hds, { force => 1, primaryOrExtended => $primaryOrExtended });
+ 0;
+ },
+ ) or return;
+
+ if ($migrate_files eq 'migrate') {
+ format_($in, $hd, $part, $all_hds) or return;
+ migrate_files($in, $hd, $part);
+ fs::mount_part($part);
+ }
+}
+
+sub Delete {
+ my ($in, $hd, $part, $all_hds) = @_;
+ if (isRAID($part)) {
+ raid::delete($all_hds->{raids}, $part);
+ } elsif (isLVM($hd)) {
+ lvm::lv_delete($hd, $part);
+ } elsif (isLoopback($part)) {
+ my $f = "$part->{loopback_device}{mntpoint}$part->{loopback_file}";
+ if (-e $f && $in->ask_yesorno('', _("Remove the loopback file?"))) {
+ unlink $f;
+ }
+ my $l = $part->{loopback_device}{loopback};
+ @$l = grep { $_ != $part } @$l;
+ delete $part->{loopback_device}{loopback} if @$l == 0;
+ fsedit::recompute_loopbacks($all_hds);
+ } else {
+ if (arch() =~ /ppc/) {
+ undef $partition_table_mac::bootstrap_part if (isAppleBootstrap($part) && ($part->{device} = $partition_table_mac::bootstrap_part));
+ }
+ partition_table::remove($hd, $part);
+ }
+}
+
+sub Type {
+ my ($in, $hd, $part) = @_;
+
+ my $warn = sub { ask_alldatawillbelost($in, $part, __("After changing type of partition %s, all data on this partition will be lost")) };
+
+ #- for ext2, warn after choosing as ext2->ext3 can be achieved without loosing any data :)
+ isExt2($part) or $warn->() or return;
+
+ my $type = type2name($part->{type});
+ $in->ask_from(_("Change partition type"),
+ _("Which filesystem do you want?"),
+ [ { label => _("Type"), val => \$type, list => [ partition_table::important_types() ], sort => 0, not_edit => !$::expert } ]) or return;
+
+ if (isExt2($part) && isThisFs('ext3', { type => name2type($type) })) {
+ my $w = $in->wait_message('', _("Switching from ext2 to ext3"));
+ if (run_program::run("tune2fs", "-j", devices::make($part->{device}))) {
+ $part->{type} = name2type($type);
+ $part->{isFormatted} = 1; #- assume that if tune2fs works, partition is formatted
+
+ #- disable the fsck (don't do it together with -j in case -j fails?)
+ fs::disable_forced_fsck($part->{device});
+ return;
+ }
+ }
+ #- either we switch to non-ext3 or switching losslessly to ext3 failed
+ !isExt2($part) or $warn->() or return;
+
+ if (defined $type) {
+ my $i_type = name2type($type);
+ fsedit::change_type(name2type($type), $hd, $part);
+ }
+}
+
+sub Mount_point {
+ my ($in, $hd, $part, $all_hds) = @_;
+
+ my $mntpoint = $part->{mntpoint} || do {
+ my $part_ = { %$part };
+ if (fsedit::suggest_part($part_, $all_hds)) {
+ fsedit::has_mntpoint('/', $all_hds) || $part_->{mntpoint} eq '/boot' ? $part_->{mntpoint} : '/';
+ } else { '' }
+ };
+ $in->ask_from(
+ '',
+ isLoopback($part) ? _("Where do you want to mount loopback file %s?", $part->{loopback_file}) :
+ _("Where do you want to mount device %s?", $part->{device}),
+ [ { label => _("Mount point"), val => \$mntpoint,
+ list => [ if_($mntpoint, $mntpoint), fsedit::suggestions_mntpoint($all_hds), '' ],
+ not_edit => !$::expert } ],
+ complete => sub {
+ !isPartOfLoopback($part) || $mntpoint or $in->ask_warn('',
+_("Can't unset mount point as this partition is used for loop back.
+Remove the loopback first")), return 1;
+ $part->{mntpoint} eq $mntpoint || check_mntpoint($in, $mntpoint, $hd, $part, $all_hds) or return 1;
+ 0;
+ }
+ ) or return;
+ $part->{mntpoint} = $mntpoint;
+}
+sub Mount_point_raw_hd {
+ my ($in, $part, $all_hds) = @_;
+
+ my $mntpoint = $part->{mntpoint};
+ $in->ask_from(
+ '',
+ _("Where do you want to mount device %s?", $part->{device}),
+ [ { label => _("Mount point"), val => \$mntpoint,
+ list => [ if_($mntpoint, $mntpoint), '' ],
+ not_edit => 0 } ],
+ complete => sub {
+ $part->{mntpoint} eq $mntpoint || check_mntpoint($in, $mntpoint, {}, $part, $all_hds) or return 1;
+ 0;
+ }
+ ) or return;
+ $part->{mntpoint} = $mntpoint;
+}
+
+sub Resize {
+ my ($in, $hd, $part) = @_;
+ my ($resize_fat, $resize_ext2, $resize_reiserfs, $block_count, $free_block, $block_size);
+ my ($min, $max) = (min_partition_size($hd), partition_table::next_start($hd, $part) - $part->{start});
+
+ if (maybeFormatted($part)) {
+ # here we may have a non-formatted or a formatted partition
+ # -> doing as if it was formatted
+
+ if (isFat($part)) {
+ write_partitions($in, $hd) or return;
+ #- try to resize without losing data
+ my $w = $in->wait_message(_("Resizing"), _("Computing FAT filesystem bounds"));
+
+ $resize_fat = resize_fat::main->new($part->{device}, devices::make($part->{device}));
+ $min = max($min, $resize_fat->min_size);
+ $max = min($max, $resize_fat->max_size);
+ } elsif (isExt2($part)) {
+ write_partitions($in, $hd) or return;
+ $resize_ext2 = devices::make($part->{device});
+ my $r = `dumpe2fs $resize_ext2 2>/dev/null`;
+ $r =~ /Block count:\s*(\d+)/ and $block_count = $1;
+ $r =~ /Free blocks:\s*(\d+)/ and $free_block = $1;
+ $r =~ /Block size:\s*(\d+)/ and $block_size = $1;
+ log::l("dumpe2fs $resize_ext2 gives: Block_count=$block_count, Free_blocks=$free_block, Block_size=$block_size");
+ if ($block_count && $free_block && $block_size) {
+ $min = max($min, ($block_count - $free_block) * $block_size / 512);
+ $max = min($max, $block_count * $block_size / 512);
+ } else {
+ $resize_ext2 = undef;
+ }
+ } elsif (isThisFs("reiserfs", $part)) {
+ write_partitions($in, $hd) or return;
+ if (defined (my $free = fs::df($part))) {
+ $resize_reiserfs = 1;
+ $min = max($min, $free);
+ }
+ }
+ #- make sure that even after normalizing the size to cylinder boundaries, the minimun will be saved,
+ #- this save at least a cylinder (less than 8Mb).
+ $min += partition_table_raw::cylinder_size($hd);
+ $min >= $max and return $in->ask_warn('', _("This partition is not resizeable"));
+
+ #- for these, we have tools to resize partition table
+ #- without losing data (or at least we hope so :-)
+ if ($resize_fat || $resize_ext2 || $resize_reiserfs) {
+ ask_alldatamaybelost($in, $part, __("All data on this partition should be backed-up")) or return;
+ } else {
+ ask_alldatawillbelost($in, $part, __("After resizing partition %s, all data on this partition will be lost")) or return;
+ }
+ }
+
+ my $mb_size = $part->{size} >> 11;
+ $in->ask_from(_("Resize"), _("Choose the new size"), [
+ { label => _("New size in MB: "), val => \$mb_size, min => $min >> 11, max => $max >> 11, type => 'range' },
+ ]) or return;
+
+
+ my $size = from_Mb($mb_size, $min, $max);
+ $part->{size} == $size and return;
+
+ my $oldsize = $part->{size};
+ $hd->{isDirty} = $hd->{needKernelReread} = 1;
+ $part->{size} = $size;
+ $hd->adjustEnd($part);
+
+ undef $@;
+ my $b = before_leaving { $@ and $part->{size} = $oldsize };
+ my $w = $in->wait_message(_("Resizing"), '');
+
+ if ($resize_fat) {
+ local *log::l = sub { $w->set(join(' ', @_)) };
+ $resize_fat->resize($part->{size});
+ } elsif ($resize_ext2) {
+ my $s = int(($part->{size} << 9) / $block_size);
+ log::l("resize2fs $resize_ext2 to size $s in block of $block_size bytes");
+ system "resize2fs", "-pf", $resize_ext2, $s;
+ } elsif ($resize_reiserfs) {
+ log::l("reiser resize to $part->{size} sectors");
+ install_any::check_prog ("resize_reiserfs") if $::isInstall;
+ system "resize_reiserfs", "-f", "-q", "-s" . $part->{size}/2 . "K", devices::make($part->{device});
+ } else {
+ $part->{notFormatted} = 1;
+ $part->{isFormatted} = 0;
+ partition_table::verifyParts($hd);
+ return;
+ }
+ $part->{isFormatted} = 1;
+ partition_table::adjust_local_extended($hd, $part);
+ partition_table::adjust_main_extended($hd);
+}
+sub Move {
+ my ($in, $hd, $part, $all_hds) = @_;
+ my $hd2 = $in->ask_from_listf(_("Move"),
+ _("Which disk do you want to move it to?"), \&partition_table::description, @{$all_hds->{hds}}) or return;
+ my $start2 = $in->ask_from_entry(_("Sector"),
+ _("Which sector do you want to move it to?"));
+ defined $start2 or return;
+
+ my $w = $in->wait_message(_("Moving"), _("Moving partition..."));
+ fsedit::move($hd, $part, $hd2, $start2);
+}
+sub Format {
+ my ($in, $hd, $part, $all_hds) = @_;
+ format_($in, $hd, $part, $all_hds);
+}
+sub Mount {
+ my ($in, $hd, $part) = @_;
+ write_partitions($in, $hd) or return;
+ fs::mount_part($part);
+}
+sub Add2RAID {
+ my ($in, $hd, $part, $all_hds) = @_;
+ my $raids = $all_hds->{raids};
+
+ local $_ = @$raids == () ? "new" :
+ $in->ask_from_list_('', _("Choose an existing RAID to add to"),
+ [ (grep {$_} map_index { $_ && "md$::i" } @$raids), __("new") ]) or return;
+
+ if (/new/) {
+ my $nb1 = raid::new($raids, $part);
+ defined modifyRAID($in, $raids, $nb1) or return raid::delete($raids, $nb1);
+ } else {
+ raid::add($raids, $part, $_);
+ }
+ raid::update(@$raids);
+ raid::stopAll();
+}
+sub Add2LVM {
+ my ($in, $hd, $part, $all_hds) = @_;
+ my $lvms = $all_hds->{lvms};
+ write_partitions($in, $_) or return foreach isRAID($part) ? @{$all_hds->{hds}} : $hd;
+
+ my $lvm = $in->ask_from_listf_('', _("Choose an existing LVM to add to"),
+ sub { ref $_[0] ? $_[0]{LVMname} : $_[0] },
+ [ @$lvms, __("new") ]) or return;
+ if (!ref $lvm) {
+ # create new lvm
+ my $name = $in->ask_from_entry('', _("LVM name?")) or return;
+ $name =~ s/\W/_/g;
+ $name = substr($name, 0, 63); # max length must be < NAME_LEN / 2 where NAME_LEN is 128
+ $lvm = bless { disks => [], LVMname => $name }, 'lvm';
+ push @$lvms, $lvm;
+ }
+ $part->{lvm} = $lvm->{LVMname};
+ push @{$lvm->{disks}}, $part;
+ delete $part->{mntpoint};
+
+ require lvm;
+ lvm::check($in) if $::isStandalone;
+ lvm::vg_add($part);
+ lvm::update_size($lvm);
+}
+sub Unmount {
+ my ($in, $hd, $part) = @_;
+ fs::umount_part($part);
+}
+sub RemoveFromRAID {
+ my ($in, $hd, $part, $all_hds) = @_;
+ raid::removeDisk($all_hds->{raids}, $part);
+}
+sub RemoveFromLVM {
+ my ($in, $hd, $part, $all_hds) = @_;
+ my $lvms = $all_hds->{lvms};
+ isPartOfLVM($part) or die;
+ my ($lvm) = grep { $_->{LVMname} eq $part->{lvm} } @$lvms;
+ lvm::vg_destroy($lvm);
+ @$lvms = grep { $_ != $lvm } @$lvms;
+}
+sub ModifyRAID {
+ my ($in, $hd, $part, $all_hds) = @_;
+ modifyRAID($in, $all_hds->{raids}, $part->{raid});
+}
+sub Loopback {
+ my ($in, $hd, $real_part, $all_hds) = @_;
+
+ write_partitions($in, $hd) or return;
+
+ my $handle = any::inspect($real_part) or $in->ask_warn('', _("This partition can't be used for loopback")), return;
+
+ my ($min, $max) = (1, loopback::getFree($handle->{dir}, $real_part));
+ my $part = { maxsize => $max, size => 0, loopback_device => $real_part, notFormatted => 1 };
+ if (!fsedit::suggest_part($part, $all_hds)) {
+ $part->{size} = $part->{maxsize};
+ $part->{type} ||= 0x483;
+ }
+ delete $part->{mntpoint}; # we don't want the suggested mntpoint
+
+ my $type = type2name($part->{type});
+ my $mb_size = $part->{size} >> 11;
+ $in->ask_from(_("Loopback"), '', [
+ { label => _("Loopback file name: "), val => \$part->{loopback_file} },
+ { label => _("Size in MB: "), val => \$mb_size, min => $min >> 11, max => $max >> 11, type => 'range' },
+ { label => _("Filesystem type: "), val => \$type, list => [ partition_table::important_types() ], not_edit => !$::expert, sort => 0 },
+ ],
+ complete => sub {
+ $part->{loopback_file} or $in->ask_warn('', _("Give a file name")), return 1, 0;
+ $part->{loopback_file} =~ s|^([^/])|/$1|;
+ if (my $size = loopback::verifFile($handle->{dir}, $part->{loopback_file}, $real_part)) {
+ $size == -1 and $in->ask_warn('', _("File already used by another loopback, choose another one")), return 1, 0;
+ $in->ask_yesorno('', _("File already exists. Use it?")) or return 1, 0;
+ delete $part->{notFormatted};
+ $part->{size} = divide($size, 512);
+ } else {
+ $part->{size} = from_Mb($mb_size, $min, $max);
+ }
+ 0;
+ }) or return;
+ $part->{type} = name2type($type);
+ push @{$real_part->{loopback}}, $part;
+ fsedit::recompute_loopbacks($all_hds);
+}
+
+sub Options {
+ my ($in, $hd, $part, $all_hds) = @_;
+
+ my @simple_options = qw(user noauto supermount);
+
+ my (undef, $user_implies) = fs::mount_options();
+ my ($options, $unknown) = fs::mount_options_unpack($part);
+ 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 => !$part->{rootDevice} && !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;
+ }
+ if ($options->{encrypted}) {
+ # modify $part->{options} for the check
+ local $part->{options};
+ fs::mount_options_pack($part, $options, $unknown);
+ if (!check($in, $hd, $part, $all_hds)) {
+ $options->{encrypted} = 0;
+ } elsif (!$part->{encrypt_key} && !isSwap($part)) {
+ if (my $encrypt_key = choose_encrypt_key($in)) {
+ $options->{'encryption='} = 'AES128';
+ $part->{encrypt_key} = $encrypt_key;
+ } else {
+ $options->{encrypted} = 0;
+ }
+ }
+ } else {
+ delete $options->{'encryption='};
+ }
+ },
+ ) or return;
+
+ fs::mount_options_pack($part, $options, $unknown);
+}
+
+
+{
+ no strict;
+ *{"Toggle to normal mode"} = sub { $::expert = 0 };
+ *{"Toggle to expert mode"} = sub { $::expert = 1 };
+ *{"Clear all"} = *Clear_all;
+ *{"Auto allocate"} = *Auto_allocate;
+ *{"Mount point"} = *Mount_point;
+ *{"Modify RAID"} = *ModifyRAID;
+ *{"Add to RAID"} = *Add2RAID;
+ *{"Remove from RAID"} = *RemoveFromRAID;
+ *{"Add to LVM"} = *Add2LVM;
+ *{"Remove from LVM"} = *RemoveFromLVM;
+ *{"Use for loopback"} = *Loopback;
+ *{"Hard drive information"} = *Hd_info;
+}
+
+
+################################################################################
+# helpers
+################################################################################
+
+sub is_part_existing {
+ my ($part, $all_hds) = @_;
+ $part && grep { fsedit::is_same_part($part, $_) } fsedit::get_all_fstab_and_holes($all_hds);
+}
+
+sub modifyRAID {
+ my ($in, $raids, $nb) = @_;
+ my $md = "md$nb";
+ $in->ask_from('', '',
+ [
+{ label => _("device"), val => \$md, list => [ map { "md$_" } grep { $nb == $_ || !$raids->[$_] } 0..8 ] },
+{ label => _("level"), val => \$raids->[$nb]{level}, list => [ qw(0 1 4 5 linear) ] },
+{ label => _("chunk size"), val => \$raids->[$nb]{'chunk-size'} },
+ ],
+ ) or return;
+ raid::updateSize($raids->[$nb]); # changing the raid level changes the size available
+ raid::changeNb($raids, $nb, first($md =~ /(\d+)/));
+}
+
+
+sub ask_alldatamaybelost {
+ my ($in, $part, $msg) = @_;
+
+ maybeFormatted($part) or return 1;
+
+ #- here we may have a non-formatted or a formatted partition
+ #- -> doing as if it was formatted
+ $in->ask_okcancel(_("Read carefully!"), [ _("Be careful: this operation is dangerous."), _($msg, $part->{device}) ], 1);
+}
+sub ask_alldatawillbelost {
+ my ($in, $part, $msg) = @_;
+
+ maybeFormatted($part) or return 1;
+
+ #- here we may have a non-formatted or a formatted partition
+ #- -> doing as if it was formatted
+ $in->ask_okcancel(_("Read carefully!"), _($msg, $part->{device}), 1);
+}
+
+sub partitions_suggestions {
+ my ($in) = @_;
+ my $t = $::expert ?
+ $in->ask_from_list_('', _("What type of partitioning?"), [ keys %fsedit::suggestions ]) :
+ 'simple';
+ $fsedit::suggestions{$t};
+}
+
+sub check_type {
+ my ($in, $type, $hd, $part) = @_;
+ eval { fsedit::check_type($type, $hd, $part) };
+ my $err = $@;
+ $in->ask_warn('', $err) if $err;
+ !$err;
+}
+sub check_mntpoint {
+ my ($in, $mntpoint, $hd, $part, $all_hds) = @_;
+ eval { fsedit::check_mntpoint($mntpoint, $hd, $part, $all_hds) };
+ local $_ = $@;
+ if (m|/boot ending on cylinder > 1024|) {
+ $in->ask_warn('',
+_("Sorry I won't accept to create /boot so far onto the drive (on a cylinder > 1024).
+Either you use LILO and it won't work, or you don't use LILO and you don't need /boot"));
+ } elsif (m|/ ending on cylinder > 1024|) {
+ $in->ask_warn('',
+_("The partition you've selected to add as root (/) is physically located beyond
+the 1024th cylinder of the hard drive, and you have no /boot partition.
+If you plan to use the LILO boot manager, be careful to add a /boot partition"));
+ undef $_;
+ } elsif (m|raid / with no /boot|) {
+ $in->ask_warn('',
+_("You've selected a software RAID partition as root (/).
+No bootloader is able to handle this without a /boot partition.
+So be careful to add a /boot partition"));
+ undef $_;
+ } elsif ($_) {
+ $in->ask_warn('', $_);
+ }
+ !$_;
+}
+sub check {
+ my ($in, $hd, $part, $all_hds) = @_;
+ check_type($in, $part->{type}, $hd, $part) &&
+ check_mntpoint($in, $part->{mntpoint}, $hd, $part, $all_hds);
+}
+
+sub write_partitions {
+ my ($in, $hd) = @_;
+ $hd->{isDirty} or return 1;
+ isLVM($hd) and return 1;
+
+ $in->ask_okcancel(_("Read carefully!"), _("Partition table of drive %s is going to be written to disk!", $hd->{device}), 1) or return;
+ if (!$::testing) {
+ partition_table::write($hd);
+ }
+ $hd->{rebootNeeded} and die _("You'll need to reboot before the modification can take place");
+ 1;
+}
+
+sub unmount {
+ my ($hd, $part) = @_;
+ fs::umount_part($part);
+}
+sub format_ {
+ my ($in, $hd, $part, $all_hds) = @_;
+ write_partitions($in, $_) or return foreach isRAID($part) ? @{$all_hds->{hds}} : $hd;
+ ask_alldatawillbelost($in, $part, __("After formatting partition %s, all data on this partition will be lost")) or return;
+ $part->{isFormatted} = 0; #- force format;
+ my $w = $in->wait_message(_("Formatting"),
+ isLoopback($part) ? _("Formatting loopback file %s", $part->{loopback_file}) :
+ _("Formatting partition %s", $part->{device}));
+ fs::format_part($all_hds->{raids}, $part);
+ 1;
+}
+
+sub need_migration {
+ my ($in, $mntpoint) = @_;
+
+ my @l = grep { $_ ne "lost+found" } all($mntpoint);
+ if (@l && $::isStandalone) {
+ my $choice;
+ my @choices = (__("Move files to the new partition"), __("Hide files"));
+ $in->ask_from('', _("Directory %s already contain some data\n(%s)", $mntpoint, formatList(5, @l)),
+ [ { val => \$choice, list => \@choices, type => 'list' } ]) or return;
+ $choice eq $choices[0] ? 'migrate' : 'hide';
+ } else {
+ 'hide';
+ }
+}
+
+sub migrate_files {
+ my ($in, $hd, $part, $all_hds) = @_;
+
+ my $wait = $in->wait_message('', _("Moving files to the new partition"));
+ my $handle = any::inspect($part, '', 'rw');
+ my @l = glob_("$part->{mntpoint}/*");
+ foreach (@l) {
+ $wait->set(_("Copying %s", $_));
+ system("cp", "-a", $_, $handle->{dir});
+ }
+ foreach (@l) {
+ $wait->set(_("Removing %s", $_));
+ system("rm", "-rf", $_);
+ }
+}
+
+
+#- unit of $mb is mega bytes, min and max are in sectors, this
+#- function is used to convert back to sectors count the size of
+#- a partition ($mb) given from the interface (on Resize or Create).
+#- modified to take into account a true bounding with min and max.
+sub from_Mb {
+ my ($mb, $min, $max) = @_;
+ $mb <= $min >> 11 and return $min;
+ $mb >= $max >> 11 and return $max;
+ $mb * 2048;
+}
+
+sub format_part_info {
+ my ($hd, $part) = @_;
+
+ my $info = '';
+
+ $info .= _("Mount point: ") . "$part->{mntpoint}\n" if $part->{mntpoint};
+ $info .= _("Device: ") . "$part->{device}\n" if $part->{device} && !isLoopback($part);
+ $info .= _("DOS drive letter: %s (just a guess)\n", $part->{device_windobe}) if $part->{device_windobe};
+ if (arch() eq "ppc") {
+ my $new_value = $part->{pType};
+ $new_value =~ s/[^A-Za-z0-9_]//g;
+ $info .= _("Type: ") . $new_value . ($::expert ? sprintf " (0x%x)", $part->{type} : '') . "\n";
+ if (defined $part->{pName}) {
+ $new_value = $part->{pName};
+ $new_value =~ s/[^A-Za-z0-9_]//g;
+ $info .= _("Name: ") . $new_value . "\n";
+ }
+ } elsif ($part->{type}) {
+ my $type = substr(type2name($part->{type}), 0, 40); # limit the length
+ $info .= _("Type: ") . $type . ($::expert ? sprintf " (0x%x)", $part->{type} : '') . "\n";
+ } else {
+ $info .= _("Empty") . "\n";
+ }
+ $info .= _("Start: sector %s\n", $part->{start}) if $::expert && !isSpecial($part);
+ $info .= _("Size: %s", formatXiB($part->{size}, 512));
+ $info .= sprintf " (%s%%)", int 100 * $part->{size} / $hd->{totalsectors} if $hd->{totalsectors};
+ $info .= _(", %s sectors", $part->{size}) if $::expert;
+ $info .= "\n";
+ $info .= _("Cylinder %d to cylinder %d\n", $part->{start} / $hd->cylinder_size(), ($part->{start} + $part->{size} - 1) / $hd->cylinder_size()) if ($::expert || !$part->{type}) && !isSpecial($part);
+ $info .= _("Formatted\n") if $part->{isFormatted};
+ $info .= _("Not formatted\n") if !$part->{isFormatted} && $part->{notFormatted};
+ $info .= _("Mounted\n") if $part->{isMounted};
+ $info .= _("RAID md%s\n", $part->{raid}) if isPartOfRAID($part);
+ $info .= sprintf "LVM %s\n", $part->{lvm} if isPartOfLVM($part);
+ $info .= _("Loopback file(s):\n %s\n", join(", ", map { $_->{loopback_file} } @{$part->{loopback}})) if isPartOfLoopback($part);
+ $info .= _("Partition booted by default\n (for MS-DOS boot, not for lilo)\n") if $part->{active} && $::expert;
+ if (isRAID($part)) {
+ $info .= _("Level %s\n", $part->{level});
+ $info .= _("Chunk size %s\n", $part->{'chunk-size'});
+ $info .= _("RAID-disks %s\n", join ", ", map { $_->{device} } @{$part->{disks}});
+ } elsif (isLoopback($part)) {
+ $info .= _("Loopback file name: %s", $part->{loopback_file});
+ }
+ if (isApple($part)) {
+ $info .= _("\nChances are, this partition is\na Driver partition, you should\nprobably leave it alone.\n");
+ }
+ if (isAppleBootstrap($part)) {
+ $info .= _("\nThis special Bootstrap\npartition is for\ndual-booting your system.\n");
+ }
+ # restrict the length of the lines
+ $info =~ s/(.{60}).*/$1.../mg;
+ $info;
+}
+
+sub format_part_info_short {
+ my ($hd, $part) = @_;
+ $part->{type} ?
+ partition_table::description($part) :
+ format_part_info($hd, $part);
+}
+
+sub format_hd_info {
+ my ($hd) = @_;
+
+ my $info = '';
+ $info .= _("Device: ") . "$hd->{device}\n";
+ $info .= _("Size: %s\n", formatXiB($hd->{totalsectors}, 512)) if $hd->{totalsectors};
+ $info .= _("Geometry: %s cylinders, %s heads, %s sectors\n", @{$hd->{geom}}{qw(cylinders heads sectors)}) if $::expert && $hd->{geom};
+ $info .= _("Info: ") . ($hd->{info} || $hd->{media_type}) . "\n" if $::expert && ($hd->{info} || $hd->{media_type});
+ $info .= _("LVM-disks %s\n", join ", ", map {$_->{device}} @{$hd->{disks}}) if isLVM($hd) && $hd->{disks};
+ $info .= _("Partition table type: %s\n", $1) if $::expert && ref($hd) =~ /_([^_]+)$/;
+ $info .= _("on bus %d id %d\n", $hd->{bus}, $hd->{id}) if $::expert && exists $hd->{bus};
+ $info;
+}
+
+sub format_raw_hd_info {
+ my ($raw_hd) = @_;
+
+ my $info = '';
+ $info .= _("Mount point: ") . "$raw_hd->{mntpoint}\n" if $raw_hd->{mntpoint};
+ $info .= format_hd_info($raw_hd);
+ if ($raw_hd->{type}) {
+ my $type = substr(type2name($raw_hd->{type}), 0, 40); # limit the length
+ $info .= _("Type: ") . $type . "\n";
+ }
+ $info .= _("Options: %s", $raw_hd->{options}) if $raw_hd->{options};
+ $info;
+}
+
+#- get the minimal size of partition in sectors to help diskdrake on
+#- limit cases, include a cylinder + start of a eventually following
+#- logical partition.
+sub min_partition_size { $_[0]->cylinder_size() + 2*$_[0]->{geom}{sectors} }
+
+
+sub choose_encrypt_key {
+ my ($in) = @_;
+
+ my ($encrypt_key, $encrypt_key2);
+ $in->ask_from_(
+ {
+ title => _("Filesystem encryption key"),
+ messages => _("Choose your filesystem encryption key"),
+ callbacks => {
+ complete => sub {
+ length $encrypt_key < 20 and $in->ask_warn('', _("This encryption key is too simple (must be at least %d characters long)", 20)), return (1,0);
+ $encrypt_key eq $encrypt_key2 or $in->ask_warn('', [ _("The encryption keys do not match"), _("Please try again") ]), return (1,1);
+ return 0
+ } } }, [
+{ label => _("Encryption key"), val => \$encrypt_key, hidden => 1 },
+{ label => _("Encryption key (again)"), val => \$encrypt_key2, hidden => 1 },
+ ]) && $encrypt_key;
+}
diff --git a/perl-install/diskdrake/removable.pm b/perl-install/diskdrake/removable.pm
new file mode 100644
index 000000000..a73c6587b
--- /dev/null
+++ b/perl-install/diskdrake/removable.pm
@@ -0,0 +1,55 @@
+package diskdrake::removable; # $Id$
+
+use diagnostics;
+use strict;
+use diskdrake::interactive;
+use common;
+use fsedit;
+use fs;
+
+sub main {
+ my ($in, $all_hds, $raw_hd) = @_;
+
+ $raw_hd = fsedit::mntpoint2part("/mnt/cdrom", [ fsedit::get_really_all_fstab($all_hds) ]);
+ my %actions = my @actions = actions();
+ my $action;
+ while ($action ne 'Done') {
+ $action = $in->ask_from_list_('',
+ diskdrake::interactive::format_raw_hd_info($raw_hd),
+ [ map { $_->[0] } group_by2 @actions ], 'Done') or return;
+ $actions{$action}->($in, $raw_hd, $all_hds);
+ }
+}
+
+sub actions {
+ (
+ __("Mount point") => \&mount_point,
+ __("Options") => \&options,
+ __("Type") => \&type,
+ __("Done") => \&done,
+ );
+}
+
+sub done {
+ my ($in, $raw_hd, $all_hds) = @_;
+ diskdrake::interactive::Done($in, $all_hds);
+}
+sub options {
+ my ($in, $raw_hd, $all_hds) = @_;
+ diskdrake::interactive::Options($in, {}, $raw_hd, $all_hds);
+}
+sub mount_point {
+ my ($in, $raw_hd, $all_hds) = @_;
+ diskdrake::interactive::Mount_point_raw_hd($in, $raw_hd, $all_hds);
+}
+sub 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;
+}
+
+1;
diff --git a/perl-install/diskdrake/removable_gtk.pm b/perl-install/diskdrake/removable_gtk.pm
new file mode 100644
index 000000000..ee61fa5b8
--- /dev/null
+++ b/perl-install/diskdrake/removable_gtk.pm
@@ -0,0 +1,31 @@
+package diskdrake::removable_gtk; # $Id$
+
+use diagnostics;
+use strict;
+
+use my_gtk qw(:helpers :wrappers :ask);
+
+
+sub per_entry_action_box {
+ my ($box, $kind, $entry) = @_;
+ $_->widget->destroy foreach $box->children;
+
+ if ($entry) {
+ 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) });
+ } group_by2 @l;
+
+ gtkadd($box, gtkadd(new Gtk::Frame(_("Choose action")),
+ createScrolledWindow(gtkpack__(new Gtk::VBox(0,0), @buttons)))) if @buttons;
+
+ } else {
+ my $txt = _("Please click on a media");
+ gtkpack($box, gtktext_insert(new Gtk::Text, $txt));
+ }
+}
diff --git a/perl-install/diskdrake/smbnfs_gtk.pm b/perl-install/diskdrake/smbnfs_gtk.pm
new file mode 100644
index 000000000..c692e11ac
--- /dev/null
+++ b/perl-install/diskdrake/smbnfs_gtk.pm
@@ -0,0 +1,247 @@
+package diskdrake::smbnfs_gtk; # $Id$
+
+use diagnostics;
+use strict;
+
+use any;
+use fs;
+use diskdrake::interactive;
+use common;
+use interactive;
+use network::smb;
+use network::nfs;
+use my_gtk qw(:helpers :wrappers :ask);
+
+my ($all_hds, $in);
+
+sub main {
+ ($in, $all_hds, my $type) = @_;
+ my ($check, $create) = $type eq 'smb' ? (\&network::smb::check, \&smb_create) : (\&network::nfs::check, \&nfs_create);
+ $check->($in) or return;
+
+ my $w = my_gtk->new('DiskDrake');
+ $create->($w->{window});
+ $w->{rwindow}->set_default_size(400, 300);
+ $w->{window}->show_all;
+ $w->main;
+}
+
+################################################################################
+# nfs/smb: helpers
+################################################################################
+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) = @_;
+ eval { $f->($in, @args, $all_hds); };
+ if (my $err = $@) {
+ $in->ask_warn(_("Error"), formatError($err));
+ }
+ Gtk->main_quit if $name eq 'Done';
+}
+
+sub per_entry_info_box {
+ my ($box, $kind, $entry) = @_;
+ $_->widget->destroy foreach $box->children;
+ my $info;
+ if ($entry) {
+ $info = diskdrake::interactive::format_raw_hd_info($entry);
+ }
+ gtkpack($box, gtkadd(new Gtk::Frame(_("Details")), gtkset_justify(new Gtk::Label($info), 'left')));
+}
+
+sub raw_hd_options {
+ my ($in, $raw_hd) = @_;
+ diskdrake::interactive::Options($in, {}, $raw_hd);
+}
+sub raw_hd_mount_point {
+ my ($in, $raw_hd) = @_;
+ diskdrake::interactive::Mount_point_raw_hd($in, $raw_hd, $all_hds);
+}
+
+
+sub per_entry_action_box {
+ my ($box, $kind, $entry) = @_;
+ $_->widget->destroy foreach $box->children;
+
+ my @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,
+ __("Done") => \&done,
+ );
+ push @buttons, map {
+ my ($txt, $f) = @$_;
+ gtksignal_connect(new Gtk::Button(translate($txt)), clicked => sub { try_($txt, $f, $entry) });
+ } group_by2(@l);
+
+ gtkadd($box, gtkpack(new Gtk::HBox(0,0), @buttons));
+}
+
+sub done {
+ my ($in) = @_;
+ diskdrake::interactive::Done($in, $all_hds);
+}
+
+sub current_entry_changed {
+ my ($kind, $entry) = @_;
+ per_entry_action_box($kind->{action_box}, $kind, $entry);
+ per_entry_info_box($kind->{info_box}, $kind, $entry);
+}
+
+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 ($curr == $click_here) {
+ $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_smbnfs {
+ my ($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},
+ );
+
+ $widget->add($kind->{main_box});
+ current_entry_changed($kind, undef);
+ $kind;
+}
+
+################################################################################
+# nfs: helpers
+################################################################################
+sub nfs2kind {
+ my ($l) = @_;
+ { type => 'nfs', name => 'NFS', val => $l, no_auto => 1 };
+}
+
+sub nfs_create {
+ my ($widget) = @_;
+
+ 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;
+ };
+ my $create = sub {
+ my ($server, $export) = @_;
+
+ my $nfs = { device => "$server:$export", type => 'nfs' };
+ fs::set_default_options($nfs);
+ $nfs;
+ };
+ add_smbnfs($widget, nfs2kind($all_hds->{nfss}), $find_servers, $find_exports, $create);
+}
+
+################################################################################
+# smb: helpers
+################################################################################
+sub smb2kind {
+ my ($l) = @_;
+ { type => 'smb', name => 'Samba', val => $l, no_auto => 1 };
+}
+
+sub smb_create {
+ my ($widget) = @_;
+
+ 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;
+ };
+ my $create = sub {
+ my ($server, $export) = @_;
+
+ my $smb = { device => "//$server/$export", type => 'smbfs', options => 'username=%' };
+ fs::set_default_options($smb);
+ $smb;
+ };
+ add_smbnfs($widget, smb2kind($all_hds->{smbs}), $find_servers, $find_exports, $create);
+}
+
+1;