From ae427550a1ab5528bd033d4b7cc2ed738a5c776a Mon Sep 17 00:00:00 2001 From: Pascal Rigaux Date: Sun, 27 Jan 2002 19:33:00 +0000 Subject: new diskdrake modules (diskdrake_interactive is now diskdrake::interactive, diskdrake is now diskdrake::hd_gtk, others created from diskdrake.pm) --- perl-install/diskdrake/hd_gtk.pm | 379 +++++++++++ perl-install/diskdrake/interactive.pm | 1112 +++++++++++++++++++++++++++++++ perl-install/diskdrake/removable.pm | 55 ++ perl-install/diskdrake/removable_gtk.pm | 31 + perl-install/diskdrake/smbnfs_gtk.pm | 247 +++++++ 5 files changed, 1824 insertions(+) create mode 100644 perl-install/diskdrake/hd_gtk.pm create mode 100644 perl-install/diskdrake/interactive.pm create mode 100644 perl-install/diskdrake/removable.pm create mode 100644 perl-install/diskdrake/removable_gtk.pm create mode 100644 perl-install/diskdrake/smbnfs_gtk.pm 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; -- cgit v1.2.1