package diskdrake; # $Id: diskdrake.pm,v 1.1.1.1 2003/06/06 09:53:06 rider Exp $ use common; use resize_fat::main; use my_gtk qw(:helpers :wrappers :ask); use partition_table qw(:types); use partition_table_raw; use detect_devices; use diskdrake_interactive; use run_program; use loopback; use devices; use network::smb; use network::nfs; use raid; use any; use log; use fsedit; use fs; my ($width, $height, $minwidth) = (400, 50, 5); my ($all_hds, $in, $current_kind, $current_entry, $update_all); my ($w, @notebook, $done_button); =begin struct { string name # which is displayed in tab of the notebook bool no_auto # wether the kind can disappear after creation string type # one of { 'hd', 'raid', 'lvm', 'loopback', 'removable', 'nfs', 'smb' } hd | hd_lvm | part_raid[] | part_loopback[] | raw_hd[] val # widget main_box widget display_box widget action_box widget info_box } current_kind part current_entry notebook current_kind[] =cut sub main { ($in, $all_hds, my $nowizard) = @_; @notebook = (); local $in->{grab} = 1; $w = my_gtk->new('DiskDrake'); my $rc = "/etc/gtk/diskdrake.rc"; -r $rc or $rc = dirname(__FILE__) . "/diskdrake.rc"; -r $rc or $rc = dirname(__FILE__) . "/share/diskdrake.rc"; Gtk::Rc->parse($rc); # TODO # is_empty_array_ref($all_hds->{raids}) or raid::stopAll; # updateLoopback(); gtkadd($w->{window}, gtkpack_(new Gtk::VBox(0,7), 0, (my $filesystems_button_box = filesystems_button_box()), 1, (my $notebook_widget = new Gtk::Notebook), 0, (my $per_kind_action_box = new Gtk::HBox(0,0)), 0, (my $general_action_box = new Gtk::HBox(0,0)), ), ); my $lock; $update_all = sub { $lock and return; $lock = 1; partition_table::assign_device_numbers($_) foreach fsedit::all_hds($all_hds); create_automatic_notebooks($notebook_widget); general_action_box($general_action_box, $nowizard); per_kind_action_box($per_kind_action_box, $current_kind); $current_kind->{type} =~ /hd|lvm|raid|loopback/ ? $filesystems_button_box->show : $filesystems_button_box->hide; current_kind_changed($current_kind); current_entry_changed($current_kind, $current_entry); $lock = 0; }; $w->sync; create_automatic_notebooks($notebook_widget); create_static_notebooks($notebook_widget); $notebook_widget->signal_connect('switch_page' => sub { $current_kind = $notebook[$_[2]]; $current_entry = ''; $update_all->(); }); $w->sync; $done_button->grab_focus; $my_gtk::pop_it = 1; $in->ask_okcancel(_("Read carefully!"), _("Please make a backup of your data first"), 1) or return if $::isStandalone; $in->ask_warn('', _("If you plan to use aboot, be carefull to leave a free space (2048 sectors is enough) at the beginning of the disk")) if (arch() eq 'alpha') and !$::isEmbedded; $w->main; } sub try { my ($name, @args) = @_; my $f = $diskdrake_interactive::{$name} or die "unknown function $name"; try_($name, \&{$f}, @args); } sub try_ { my ($name, $f, @args) = @_; fsedit::undo_prepare($all_hds) if $name ne 'Undo'; my $v = eval { $f->($in, @args, $all_hds); }; if (my $err = $@) { $err =~ /setstep/ and die ''; $in->ask_warn(_("Error"), common::formatError($err)); } if ($current_kind->{type} =~ /hd|lvm|raid|loopback/) { $current_entry = '' if !diskdrake_interactive::is_part_existing($current_entry, $all_hds); } $update_all->(); if ($v && member($name, 'Done', 'Wizard')) { $::isEmbedded ? kill( 'USR1', $::CCPID) : Gtk->main_quit; } } ################################################################################ # generic: helpers ################################################################################ sub add_kind2notebook { my ($notebook_widget, $kind) = @_; die if $kind->{main_box}; $kind->{display_box} = gtkset_usize(new Gtk::HBox(0,0), $width, $height); $kind->{action_box} = gtkset_usize(new Gtk::VBox(0,0), 130, 150); $kind->{info_box} = new Gtk::VBox(0,0); $kind->{main_box} = gtkpack_(new Gtk::VBox(0,7), 0, $kind->{display_box}, 1, gtkpack_(new Gtk::HBox(0,7), 0, $kind->{action_box}, 1, $kind->{info_box})); my_gtk::add2notebook($notebook_widget, $kind->{name}, $kind->{main_box}); push @notebook, $kind; $kind; } sub general_action_box { my ($box, $nowizard) = @_; $_->widget->destroy foreach $box->children; my @actions = (if_($::isInstall && !$nowizard, __("Wizard")), diskdrake_interactive::general_possible_actions($in, $all_hds), __("Done")); foreach my $s (@actions) { my $button = new Gtk::Button(translate($s)); $done_button = $button if $s eq 'Done'; gtkadd($box, gtksignal_connect($button, clicked => sub { try($s) })); } } sub per_kind_action_box { my ($box, $kind) = @_; $_->widget->destroy foreach $box->children; $kind or return; if ($kind->{type} =~ /hd|lvm|raid|loopback/) { foreach my $s (diskdrake_interactive::hd_possible_actions($in, kind2hd($kind), $all_hds)) { gtkadd($box, gtksignal_connect(new Gtk::Button(translate($s)), clicked => sub { try($s, kind2hd($kind)) })); } } elsif ($kind->{type} =~ 'nfs|smb') { my $f = $kind->{type} eq 'nfs' ? \&nfs_create : \&smb_create; gtkadd($box, gtksignal_connect(new Gtk::Button(translate(__("New"))), clicked => sub { try_('', $f, $kind) })); } } sub per_entry_action_box { my ($box, $kind, $entry) = @_; $_->widget->destroy foreach $box->children; if ($entry) { my @buttons; if ($kind->{type} =~ /hd|lvm|raid|loopback/) { @buttons = map { my $s = $_; my $w = new Gtk::Button(translate($s)); $w->signal_connect(clicked => sub { try($s, kind2hd($kind), $entry) }); $w; } diskdrake_interactive::part_possible_actions($in, kind2hd($kind), $entry, $all_hds); } elsif ($kind->{type} =~ /removable|nfs|smb/) { my %l = ( if_($kind->{type} eq 'smb', _("Remote") => \&smb_remote, ), if_($kind->{type} eq 'nfs', _("Remote") => \&nfs_remote, ), _("Mount point") => \&raw_hd_mount_point, _("Options") => \&raw_hd_options, if_($kind->{type} eq 'removable', _("Type") => \&removable_type, ), ); @buttons = map_each { my ($txt, $f) = @_; gtksignal_connect(new Gtk::Button($txt), clicked => sub { try_('', $f, $entry) }); } %l; if ($kind->{type} =~ /nfs|smb/) { push @buttons, map { my $s = $_; gtksignal_connect(new Gtk::Button(translate($s)), clicked => sub { try($s, {}, $entry) }); } if_($entry->{isMounted}, __("Unmount")), if_($entry->{mntpoint} && !$entry->{isMounted}, __("Mount")), ; } } gtkadd($box, gtkadd(new Gtk::Frame(_("Choose action")), createScrolledWindow(gtkpack__(new Gtk::VBox(0,0), @buttons)))) if @buttons; } else { my $txt; if ($kind->{type} =~ /hd|lvm|raid|loopback/) { $txt = !$::isStandalone && fsedit::is_one_big_fat($all_hds->{hds}) ? _("You have one big FAT partition (generally used by MicroSoft Dos/Windows). I suggest you first resize that partition (click on it, then click on \"Resize\")") : _("Please click on a partition"); } elsif ($kind->{type} =~ /removable/) { $txt = _("Please click on a media"); } elsif ($kind->{type} =~ /nfs|smb/) { $txt = @{$kind->{val}} ? _("Please click on a button above\n\nOr use \"New\"") : _("Use \"New\""); } gtkpack($box, gtktext_insert(new Gtk::Text, $txt)); } } sub per_entry_info_box { my ($box, $kind, $entry) = @_; $_->widget->destroy foreach $box->children; my $info; if ($kind->{type} =~ /hd|lvm|raid|loopback/) { if ($entry) { $info = diskdrake_interactive::format_part_info(kind2hd($kind), $entry); } elsif ($kind->{type} =~ /hd|lvm/) { $info = diskdrake_interactive::format_hd_info($kind->{val}); } } elsif ($kind->{type} =~ /removable|nfs|smb/ && $entry) { $info = diskdrake_interactive::format_raw_hd_info($entry); } gtkpack($box, gtkadd(new Gtk::Frame(_("Details")), gtkset_justify(new Gtk::Label($info), 'left'))); } sub current_kind_changed { my ($kind) = @_; $_->widget->destroy foreach $kind->{display_box}->children; if ($kind->{type} =~ /hd|lvm|raid|loopback/) { my $v = $kind->{val}; my @parts = $kind->{type} eq 'raid' ? grep {$_} @$v : $kind->{type} eq 'loopback' ? @$v : fsedit::get_fstab_and_holes($v); my $totalsectors = $kind->{type} =~ /raid|loopback/ ? sum(map { $_->{size} } @parts) : $v->{totalsectors}; create_buttons4partitions($kind, $totalsectors, @parts); } elsif ($kind->{type} =~ /removable|nfs|smb/) { create_buttons4raw_hd($kind, $kind->{val}); } } sub current_entry_changed { my ($kind, $entry) = @_; $current_entry = $entry; if ($kind) { per_entry_action_box($kind->{action_box}, $kind, $entry); per_entry_info_box($kind->{info_box}, $kind, $entry); } } sub create_static_notebooks { my ($notebook_widget) = @_; if ($::isStandalone) { add_kind2notebook($notebook_widget, removable2kind($all_hds->{raw_hds})) if @{$all_hds->{raw_hds}}; add_kind2notebook($notebook_widget, smb2kind($all_hds->{smbs})); add_kind2notebook($notebook_widget, nfs2kind($all_hds->{nfss})); } } sub create_automatic_notebooks { my ($notebook_widget) = @_; my @l = fsedit::all_hds($all_hds); $_->{marked} = 0 foreach @notebook; my $may_add = sub { my ($kind) = @_; my @l = grep { $kind->{val} == $_->{val} } @notebook; @l > 1 and log::l("weird: create_automatic_notebooks"); $kind = $l[0] || add_kind2notebook($notebook_widget, $kind); $kind->{marked} = 1; }; $may_add->(hd2kind($_)) foreach @{$all_hds->{hds}}; $may_add->(lvm2kind($_)) foreach @{$all_hds->{lvms}}; $may_add->(raid2kind()) if grep {$_} @{$all_hds->{raids}}; $may_add->(loopback2kind()) if @{$all_hds->{loopbacks}}; @notebook = grep_index { my $b = $_->{no_auto} || $_->{marked} or $notebook_widget->remove_page($::i); $b; } @notebook; @notebook or die ''; } ################################################################################ # parts: helpers ################################################################################ sub create_buttons4partitions { my ($kind, $totalsectors, @parts) = @_; $width = max($width, 0.9 * second($w->{window}->window->get_size)) if $w->{window}->window; my $ratio = $totalsectors ? ($width - @parts * $minwidth) / $totalsectors : 1; while (1) { my $totalwidth = sum(map { $_->{size} * $ratio + $minwidth } @parts); $totalwidth <= $width and last; $ratio /= $totalwidth / $width * 1.1; } foreach my $entry (@parts) { my $w = new Gtk::Button($entry->{mntpoint} || '') or die ''; $w->signal_connect(focus_in_event => sub { current_entry_changed($kind, $entry) }); $w->signal_connect(button_press_event => sub { current_entry_changed($kind, $entry) }); $w->signal_connect(key_press_event => sub { my ($w, $e) = @_; $e->{state} & 4 or return; my $c = chr $e->{keyval}; foreach my $s (diskdrake_interactive::part_possible_actions($in, kind2hd($kind), $entry, $all_hds)) { ${{ Create => 'c', Delete => 'd', Format => 'f', Loopback => 'l', Resize => 'r', Type => 't', Mount => 'M', Unmount => 'u', 'Mount point' => 'm', 'Add to LVM' => 'L', 'Remove from LVM' => 'L', 'Add to RAID' => 'R', 'Remove from RAID' => 'R', }}{$s} eq $c or next; try($s, kind2hd($kind), $entry); last; } }); $w->set_name("PART_" . type2name($entry->{type})); $w->set_usize($entry->{size} * $ratio + $minwidth, 0); gtkpack__($kind->{display_box}, $w); $w->grab_focus if $current_entry && fsedit::is_same_part($current_entry, $entry); } } ################################################################################ # disks: helpers ################################################################################ sub current_hd { $current_kind->{type} eq 'hd' or die 'current_hd called but $current_kind is not an hd'; $current_kind->{val}; } sub current_part { current_hd(); $current_entry; } sub kind2hd { my ($kind) = @_; $kind->{type} =~ /hd|lvm/ ? $kind->{val} : {} } sub hd2kind { my ($hd) = @_; { type => 'hd', name => $hd->{device}, val => $hd }; } sub filesystems_button_box() { my @types = (__("Ext2"), __("Journalised FS"), __("Swap"), arch() =~ /sparc/ ? __("SunOS") : arch() eq "ppc" ? __("HFS") : __("FAT"), __("Other"), __("Empty")); my %name2type = (Ext2 => 0x83, 'Journalised FS' => 0x483, Swap => 0x82, Other => 1, FAT => 0xb, HFS => 0x402); gtkpack(new Gtk::HBox(0,0), _("Filesystem types:"), map { my $w = new Gtk::Button(translate($_)); my $t = $name2type{$_}; $w->signal_connect(clicked => sub { try_('', \&createOrChangeType, $t, current_hd(), current_part()) }); $w->can_focus(0); $w->set_name($_); $w; } @types); } sub createOrChangeType { my ($in, $type, $hd, $part, $all_hds) = @_; $part ||= !fsedit::get_fstab($hd) && { type => 0, start => 1, size => $hd->{totalsectors} - 1 }; $part or return; if ($type == 1) { $in->ask_warn('', _("Use ``%s'' instead", $part->{type} ? _("Type") : _("Create"))); } elsif (!$type) { $in->ask_warn('', _("Use ``%s'' instead", _("Delete"))) if $part->{type}; } elsif ($part->{type}) { return unless $::expert; return if $type == $part->{type}; isBusy($part) and $in->ask_warn('', _("Use ``Unmount'' first")), return; diskdrake_interactive::ask_alldatawillbelost($in, $part, __("After changing type of partition %s, all data on this partition will be lost")) or return; fsedit::change_type($type, $hd, $part); } else { $part->{type} = $type; diskdrake_interactive::Create($in, $hd, $part, $all_hds); } } ################################################################################ # lvms: helpers ################################################################################ sub lvm2kind { my ($lvm) = @_; { type => 'lvm', name => $lvm->{LVMname}, val => $lvm }; } ################################################################################ # raids: helpers ################################################################################ sub raid2kind { { type => 'raid', name => 'raid', val => $all_hds->{raids} }; } ################################################################################ # loopbacks: helpers ################################################################################ sub loopback2kind { { type => 'loopback', name => 'loopback', val => $all_hds->{loopbacks} }; } ################################################################################ # removable/nfs/smb: helpers ################################################################################ sub create_buttons4raw_hd { my ($kind, $l) = @_; foreach my $entry (@$l) { my $w = new Gtk::Button($entry->{device}) or die ''; $w->signal_connect(focus_in_event => sub { current_entry_changed($kind, $entry) }); $w->signal_connect(button_press_event => sub { current_entry_changed($kind, $entry) }); $w->set_name("PART_raw_Linux"); gtkpack__($kind->{display_box}, $w); $w->grab_focus if $current_entry && fsedit::is_same_hd($current_entry, $entry); } } sub raw_hd_mount_point { my ($in, $raw_hd) = @_; my $mntpoint = $raw_hd->{mntpoint}; $in->ask_from( '', _("Where do you want to mount device %s?", $raw_hd->{device}), [ { label => _("Mount point"), val => \$mntpoint, list => [ if_($mntpoint, $mntpoint), '' ], not_edit => 0 } ], complete => sub { $raw_hd->{mntpoint} eq $mntpoint || diskdrake_interactive::check_mntpoint($in, $mntpoint, {}, $raw_hd, $all_hds) or return 1; 0; } ) or return; $raw_hd->{mntpoint} = $mntpoint; } sub raw_hd_options { my ($in, $raw_hd) = @_; my @simple_options = qw(user noauto supermount); my (undef, $user_implies) = fs::mount_options(); my ($options, $unknown) = fs::mount_options_unpack($raw_hd); my %help = fs::mount_options_help(keys %$options); my $prev_user = $options->{user}; $in->ask_from(_("Mount options"), '', [ (map {; { label => $_, text => formatAlaTeX($help{$_}), val => \$options->{$_}, advanced => !member($_, @simple_options), if_(!/=$/, type => 'bool'), } } keys %$options), { label => _("Various"), val => \$unknown, advanced => 1 }, ], changed => sub { if ($prev_user != $options->{user}) { $prev_user = $options->{user}; $options->{$_} = $options->{user} foreach @$user_implies; } }, ) or return; fs::mount_options_pack($raw_hd, $options, $unknown); } ################################################################################ # removable: helpers ################################################################################ sub removable2kind { my ($medias) = @_; { type => 'removable', name => _("Removable media"), val => $medias, no_auto => 1 }; } sub removable_type { my ($in, $raw_hd) = @_; my @fs = ('auto', fs::auto_fs()); my $type = $raw_hd->{type}; $in->ask_from(_("Change type"), _("Which filesystem do you want?"), [ { label => _("Type"), val => \$type, list => [@fs], not_edit => !$::expert } ]) or return; $raw_hd->{type} = $type; } ################################################################################ # nfs: helpers ################################################################################ sub nfs2kind { my ($l) = @_; { type => 'nfs', name => 'NFS', val => $l, no_auto => 1 }; } sub nfs_create { my ($in, $kind) = @_; network::nfs::check($in) or return; my $nfs = { type => 'nfs' }; nfs_remote($in, $nfs) or return; raw_hd_mount_point($in, $nfs) or return; fs::set_default_options($nfs); push @{$kind->{val}}, $nfs; } sub nfs_remote { my ($in, $nfs) = @_; my ($server_txt, $name_txt) = $nfs->{device} =~ m|(.*?):(.*)|; my $max_servers_nb_for_probing_all = 10; my $wait = $in->wait_message('', _("Scanning available nfs shared resource")); my @servers = network::nfs::find_servers(); my @l = map { my $server = $_; $wait->set(_("Scanning available nfs shared resource of server %s", $server->{name})); map {; { server => $server, %$_ } } network::nfs::find_exports($server); } @servers; undef $wait; my ($remote) = grep { $_->{name} eq $name_txt && $_->{server}{name} eq $server_txt } @l; ($server_txt, $name_txt) = ('', ''); my $format = sub { ($_[0]{server}{name} || $_[0]{server}{ip}) . ":" . $_[0]{name} . ($_[0]{comment} ? " ($_[0]{comment})" : '') }; $in->ask_from_({ messages => "Remote", advanced_messages => _("If the list above doesn't contain the wanted entry, enter it here:"), }, [ if_(@l > 0, { val => \$remote, separator => ":", format => $format, list => \@l, disabled => sub { $server_txt } }), { label => _("Server"), val => \$server_txt, advanced => @l > 0 }, { label => _("Shared resource"), val => \$name_txt, advanced => @l > 0 }, ]) or return; ($server_txt, $name_txt) = ($remote->{server}{name}, $remote->{name}) if !$server_txt; $nfs->{device} = $server_txt . ':' . $name_txt; } ################################################################################ # smb: helpers ################################################################################ sub smb2kind { my ($l) = @_; { type => 'smb', name => 'Samba', val => $l, no_auto => 1 }; } sub smb_create { my ($in, $kind) = @_; network::smb::check($in) or return; my $smb = { type => 'smbfs', options => 'username=%' }; smb_remote($in, $smb) or return; raw_hd_mount_point($in, $smb) or return; fs::set_default_options($smb); push @{$kind->{val}}, $smb; } sub smb_remote { my ($in, $smb) = @_; my ($server_txt, $name_txt) = $smb->{device} =~ m|//(.*?)/(.*)|; my $max_servers_nb_for_probing_all = 50; my $wait = $in->wait_message('', _("Scanning available samba shared resource")); my @servers = network::smb::find_servers(); my ($server, $name); if (@servers > $max_servers_nb_for_probing_all) { my @l; ($server) = grep { $_->{name} eq $server_txt } @servers; my $format = sub { "$_[0]{group}/$_[0]{name}" }; do { $server = $in->ask_from_treelistf('', "Choose a server", "/", $format, \@servers, $server) or return; @l = do { $wait->set(_("Scanning available samba shared resource of server %s", $server->{name})); network::smb::find_exports($server); }; @l or $in->ask_warn('', sprintf("Server %s doesn't export any resource", $server->{name})); } until @l; undef $wait; ($name) = grep { $_->{name} eq $name_txt } @l; my $format2 = sub { "$_[0]{name} ($_[0]{comment})" }; $name = $in->ask_from_listf('', "Choose a shared directory", $format2, \@l, $name) or return; } else { my @l = map { my $server = $_; $wait->set(_("Scanning available samba shared resource of server %s", $server->{name})); map {; { server => $server, %$_ } } network::smb::find_exports($server) } @servers; undef $wait; my ($remote) = grep { $_->{name} eq $name_txt && $_->{server}{name} eq $server_txt } @l; ($server_txt, $name_txt) = ('', ''); my $format = sub { "$_[0]{server}{group}/$_[0]{server}{name}/$_[0]{name}" }; $in->ask_from_({ messages => "Remote", advanced_messages => _("If the list above doesn't contain the wanted entry, enter it here:"), }, [ if_(@l > 0, { val => \$remote, separator => "/", format => $format, list => \@l, disabled => sub { $server_txt } }), { label => _("Server"), val => \$server_txt, advanced => @l > 0 }, { label => _("Shared resource"), val => \$name_txt, advanced => @l > 0 }, ]) or return; ($server_txt, $name_txt) = ($remote->{server}{name}, $remote->{name}) if !$server_txt; } $smb->{device} = '//' . $server_txt . '/' . $name_txt; } 1;