summaryrefslogtreecommitdiffstats
path: root/perl-install/raid.pm
blob: df2cb6cde2b686032703f01846bac84b6b033bbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package raid; # $Id$

use diagnostics;
use strict;

#-######################################################################################
#- misc imports
#-######################################################################################
use common;
use partition_table qw(:types);
use run_program;
use devices;
use modules;
use fs;

sub max_nb() { 31 }

sub nb { 
    my ($nb) = @_;
    first((ref($nb) ? $nb->{device} : $nb) =~ /(\d+)/);
}

sub new {
    my ($raids, @parts) = @_;
    my $nb = @$raids; 
    $raids->[$nb] = { 'chunk-size' => "64k", type => 0x483, disks => [ @parts ], device => "md$nb", notFormatted => 1, level => 1 };
    foreach my $part (@parts) {
	$part->{raid} = $nb;
	delete $part->{mntpoint};
    }
    update($raids->[$nb]);
    $nb;
}

sub add {
    my ($raids, $part, $nb) = @_; $nb = nb($nb);
    $raids->[$nb]{isMounted} and die \N("Can't add a partition to _formatted_ RAID md%d", $nb);
    inactivate_and_dirty($raids->[$nb]);
    $part->{notFormatted} = 1; $part->{isFormatted} = 0;
    $part->{raid} = $nb;
    delete $part->{mntpoint};
    push @{$raids->[$nb]{disks}}, $part;
    update($raids->[$nb]);
}

sub delete {
    my ($raids, $nb) = @_;
    $nb = nb($nb);
    inactivate_and_dirty($raids->[$nb]);
    delete $_->{raid} foreach @{$raids->[$nb]{disks}};
    undef $raids->[$nb];
}

sub changeNb {
    my ($raids, $oldnb, $newnb) = @_;
    if ($oldnb != $newnb) {
	inactivate_and_dirty($raids->[$_]) foreach $oldnb, $newnb;

	($raids->[$newnb], $raids->[$oldnb]) = ($raids->[$oldnb], undef);
	$raids->[$newnb]{device} = "md$newnb";
	$_->{raid} = $newnb foreach @{$raids->[$newnb]{disks}};
    }
    $newnb;
}

sub removeDisk {
    my ($raids, $part) = @_;
    my $nb = nb($part->{raid});
    inactivate_and_dirty($raids->[$nb]);
    delete $part->{raid};
    my $disks = $raids->[$nb]{disks};
    @$disks = grep { $_ != $part } @$disks;
    if (@$disks) {
	update($raids->[$nb]);
    } else {
	undef $raids->[$nb];
    }
}

sub updateSize {
    my ($part) = @_;
    local $_ = $part->{level};
    my @l = map { $_->{size} } @{$part->{disks}};

    $part->{size} = do {
	if (/0|linear/) { sum @l        }
	elsif (/1/)     { min @l        }
	elsif (/4|5/)   { min(@l) * $#l }
    };
}

sub module {
    my ($part) = @_;
    my $mod = $part->{level};

    $mod = 5 if $mod eq "4";
    $mod = "raid$mod" if $mod =~ /^\d+$/;
    $mod;
}


sub update {
    updateSize($_) foreach @_;
}

sub write {
    my ($raids, $file) = @_;
    return if $::testing;

    output($file,
	   map {
	       my $s = sprintf(<<EOF, devices::make($_->{device}), $_->{level}, $_->{'chunk-size'}, int @{$_->{disks}});
raiddev       %s
raid-level    %s
chunk-size    %s
persistent-superblock 1
nr-raid-disks %d
EOF
	       my @devs = map_index { 
		   "    device    " . devices::make($_->{device}) . "\n    raid-disk $::i\n";
	       } @{$_->{disks}};

	       $s, @devs
	   } grep { $_ } @$raids);
}

sub make {
    my ($raids, $part) = @_;    

    return if is_active($part->{device});

    inactivate_and_dirty($part);

    isRAID($_) and make($raids, $_) foreach @{$part->{disks}};
    my $dev = devices::make($part->{device});
    eval { modules::load(module($part)) };
    &write($raids, "/etc/raidtab");
    run_program::run("mkraid", "--really-force", $dev) or die
	$::isStandalone ? N("mkraid failed (maybe raidtools are missing?)") : N("mkraid failed");
}

sub format_part {
    my ($raids, $part) = @_;
    $part->{isFormatted} and return;

    make($raids, $part);
    fs::real_format_part($part);
    $_->{isFormatted} = 1 foreach @{$part->{disks}};
}

sub verify {
    my ($raids) = @_;
    $raids or return;
    foreach (grep { $_ } @$raids) {
	@{$_->{disks}} >= ($_->{level} =~ /4|5/ ? 3 : 2) or die \N("Not enough partitions for RAID level %d\n", $_->{level});
    }
}

sub prepare_prefixed {
    my ($raids, $prefix) = @_;
    $raids or return;

    &write($raids, "/etc/raidtab") if ! -e "/etc/raidtab";
    
    eval { cp_af("/etc/raidtab", "$prefix/etc/raidtab") };
    foreach (grep { $_ } @$raids) {
	devices::make("$prefix/dev/$_->{device}") foreach @{$_->{disks}};
    }
}

sub inactivate_and_dirty {
    my ($part) = @_;
    run_program::run("raidstop", devices::make($part->{device}));
    $part->{notFormatted} = 1; $part->{isFormatted} = 0;
}

sub active_mds() {
    map { if_(/^(md\d+) /, $1) } cat_("/proc/mdstat");
}

sub is_active {
    my ($dev) = @_;
    member($dev, active_mds());
}

sub inactivate_all() { run_program::run("raidstop", devices::make($_)) foreach active_mds() }

1;
='#n975'>975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
package any; # $Id$

use diagnostics;
use strict;

#-######################################################################################
#- misc imports
#-######################################################################################
use common;
use detect_devices;
use partition_table qw(:types);
use fsedit;
use fs;
use lang;
use run_program;
use keyboard;
use devices;
use modules;
use log;
use c;

sub drakx_version() { 
    sprintf "DrakX v%s built %s", $::testing ? ('TEST', scalar gmtime()) : (split('/', cat_("$ENV{SHARE_PATH}/VERSION")))[2,3];
}

sub facesdir() {
    "$::prefix/usr/share/mdk/faces/";
}
sub face2png {
    my ($face) = @_;
    facesdir() . $face . ".png";
}
sub facesnames() {
    my $dir = facesdir();
    my @l = grep { /^[A-Z]/ } all($dir);
    map { if_(/(.*)\.png/, $1) } (@l ? @l : all($dir));
}

sub addKdmIcon {
    my ($user, $icon) = @_;
    my $dest = "$::prefix/usr/share/faces/$user.png";
    eval { cp_af(facesdir() . $icon . ".png", $dest) } if $icon;
}

sub allocUsers {
    my ($users) = @_;
    my @m = my @l = facesnames();
    foreach (grep { !$_->{icon} || $_->{icon} eq "automagic" } @$users) {
	$_->{auto_icon} = splice(@m, rand(@m), 1); #- known biased (see cookbook for better)
	log::l("auto_icon is $_->{auto_icon}");
	@m = @l unless @m;
    }
}

sub addUsers {
    my ($users) = @_;

    allocUsers($users);
    foreach my $u (@$users) {
	run_program::rooted($::prefix, "usermod", "-G", join(",", @{$u->{groups}}), $u->{name}) if !is_empty_array_ref($u->{groups});
	addKdmIcon($u->{name}, delete $u->{auto_icon} || $u->{icon});
    }
}

sub crypt {
    my ($password, $md5) = @_;
    crypt($password, $md5 ? '$1$' . salt(8) : salt(2));
}
sub enableShadow() {
    run_program::rooted($::prefix, "pwconv")  or log::l("pwconv failed");
    run_program::rooted($::prefix, "grpconv") or log::l("grpconv failed");
}

sub hdInstallPath() {
    my $tail = first(readlink("/tmp/image") =~ m|^/tmp/hdimage/?(.*)|);
    my $head = first(readlink("/tmp/hdimage") =~ m|$::prefix(.*)|);
    defined $tail && ($head ? "$head/$tail" : "/mnt/hd/$tail");
}

sub kernelVersion() {
    my $kernel = readlink("$::prefix/boot/vmlinuz") || first(all("$::prefix/boot"));
    first($kernel =~ /vmlinuz-(.*)/);
}

sub mkbootdisk {
    my ($in, $bootloader, $fstab) = @_;

    if (arch() =~ /sparc/) {
	#- as probing floppies is a bit more different on sparc, assume always /dev/fd0.
	#- [pixel] uh, but in that case it would be better to change detect_devices::floppies, no?
	$in->ask_okcancel('',
			 N("A custom bootdisk provides a way of booting into your Linux system without
depending on the normal bootloader. This is useful if you don't want to install
SILO on your system, or another operating system removes SILO, or SILO doesn't
work with your hardware configuration. A custom bootdisk can also be used with
the Mandrake rescue image, making it much easier to recover from severe system
failures.

If you want to create a bootdisk for your system, insert a floppy in the first
drive and press \"Ok\".")) or return;
    } else {
	$in->ask_yesorno('', formatAlaTeX(
			    N("A custom bootdisk provides a way of booting into your Linux system without
depending on the normal bootloader. This is useful if you don't want to install
LILO (or grub) on your system, or another operating system removes LILO, or LILO doesn't
work with your hardware configuration. A custom bootdisk can also be used with
the Mandrake rescue image, making it much easier to recover from severe system
failures. Would you like to create a bootdisk for your system?
%s", isThisFs('xfs', fsedit::get_root($fstab)) ? N("

(WARNING! You're using XFS for your root partition,
creating a bootdisk on a 1.44 Mb floppy will probably fail,
because XFS needs a very large driver).") : ''))) or return;
    }

    my $floppy_dev;
    my @l = detect_devices::floppies_dev() or die \N("Sorry, no floppy drive available");
    my %l = (
	     'fd0'  => N("First floppy drive"),
	     'fd1'  => N("Second floppy drive"),
	     'Skip' => N("Skip"),
	    );
    my $format = sub { $l{$_[0]} || $_[0] };

    $in->ask_from_({
	       messages => N("Choose the floppy drive you want to use to make the bootdisk"),
	      }, [ { val => \$floppy_dev, list => \@l, format => $format } ]
        ) or return;

    $in->ask_warn('', N("Insert a floppy in %s", $format->($floppy_dev)));

    my $_w = $in->wait_message('', N("Creating bootdisk..."));

    require bootloader;
    bootloader::mkbootdisk(kernelVersion(), $floppy_dev, $bootloader->{perImageAppend});
    1;
}

sub setupBootloader {
    my ($in, $b, $all_hds, $fstab, $security) = @_;
    my $hds = $all_hds->{hds};

    require bootloader;
  general:
    setupBootloader__general($in, $b, $all_hds, $fstab, $security) or return 0;
    setupBootloader__boot_bios_drive($in, $b, $hds) or goto general;

    setupBootloader__entries($in, $b, $all_hds, $fstab) or goto general;

    #- somewhere should bootloader really installed ?
    $::isStandalone and my $_w = $in->wait_message(N("Please wait"), N("Bootloader installation in progress"));

    eval { run_program::rooted($::prefix, 'lilo', '-u') } if $::isInstall && !$::o->{isUpgrade} && -e "$::prefix/etc/lilo.conf" && glob("$::prefix/boot/boot.*");

    bootloader::install($b, $fstab, $hds);
}


sub setupBootloader_simple {
    my ($in, $b, $all_hds, $fstab, $security) = @_;
    my $hds = $all_hds->{hds};

    require bootloader;
    my $mixed_kind_of_disks = bootloader::mixed_kind_of_disks($hds);
    #- full expert questions when there is 2 kind of disks
    #- it would need a semi_auto asking on which drive the bios boots...

    $mixed_kind_of_disks || $b->{bootUnsafe} || arch() =~ /ppc/ or return 1; #- default is good enough
    
    if (!$mixed_kind_of_disks && arch() !~ /ia64/) {
	setupBootloader__mbr_or_not($in, $b, $hds, $fstab) or return 0;
    } else {
      general:
	setupBootloader__general($in, $b, $all_hds, $fstab, $security) or return 0;
    }
    setupBootloader__boot_bios_drive($in, $b, $hds) or goto general;
    1;
}


sub setupBootloader__boot_bios_drive {
    my ($in, $b, $hds) = @_;

    bootloader::mixed_kind_of_disks($hds) && 
      $b->{boot} =~ /\d$/ && #- on a partition
	is_empty_hash_ref($b->{bios}) && #- some bios mapping already there
	  arch() !~ /ppc/ or return 1;

    log::l("mixed_kind_of_disks");
    my $hd = $in->ask_from_listf('', N("You decided to install the bootloader on a partition.
This implies you already have a bootloader on the hard drive you boot (eg: System Commander).

On which drive are you booting?"), \&partition_table::description, $hds) or return 0;
    log::l("mixed_kind_of_disks chosen $hd->{device}");
    $b->{first_hd_device} = "/dev/$hd->{device}";
    1;
}

sub setupBootloader__mbr_or_not {
    my ($in, $b, $hds, $fstab) = @_;

    if (arch() =~ /ppc/) {
	if (defined $partition_table::mac::bootstrap_part) {
	    $b->{boot} = $partition_table::mac::bootstrap_part;
	    log::l("set bootstrap to $b->{boot}"); 
	} else {
	    die "no bootstrap partition - yaboot.conf creation failed";
	}
    } else {
	my $floppy = detect_devices::floppy();

	my @l = (
		 [ N("First sector of drive (MBR)") => '/dev/' . $hds->[0]{device} ],
		 [ N("First sector of the root partition") => '/dev/' . fsedit::get_root($fstab, 'boot')->{device} ],
		     if_($floppy, 
                 [ N("On Floppy") => "/dev/$floppy" ],
		     ),
		 [ N("Skip") => '' ],
		);

	my $default = arch() =~ /sparc/ ? ($b->{use_partition} ? $l[1] : $l[0]) : 
	                                  find { $_->[1] eq $b->{boot} } @l;
	$in->ask_from_({ title => arch() =~ /sparc/ ? N("SILO Installation") : N("LILO/grub Installation"),
			 messages => N("Where do you want to install the bootloader?"),
			 interactive_help_id => 'setupBootloaderBeginner',
		       },
		      [ { val => \$default, list => \@l, format => sub { $_[0][0] }, type => 'list' } ]);
	my $new_boot = $default->[1] or return;

	if (arch() =~ /sparc/) {
	    $b->{use_partition} = $new_boot eq $l[1][1];
	}  else {
	    #- remove bios mapping if the user changed the boot device
	    delete $b->{bios} if $new_boot ne $b->{boot};

	    $b->{boot} = $new_boot;
	}
    }
    1;
}

sub setupBootloader__general {
    my ($in, $b, $all_hds, $fstab, $security) = @_;

    my @silo_install_lang = (N("First sector of drive (MBR)"), N("First sector of boot partition"));

    ($b->{method}, my $method_choices) = bootloader::method_choices($fstab, $b);
    my $profiles = bootloader::has_profiles($b);
    my $prev_force_acpi = my $force_acpi = bootloader::get_append($b, 'acpi') !~ /off|ht/;
    my $prev_force_noapic = my $force_noapic = bootloader::get_append($b, 'noapic');
    my $memsize = bootloader::get_append($b, 'mem');
    my $prev_clean_tmp = my $clean_tmp = any { $_->{mntpoint} eq '/tmp' } @{$all_hds->{special} ||= []};
    my $prev_boot = $b->{boot};
    my $mkbootdisk;

    $b->{password2} ||= $b->{password} ||= '';
    $b->{vga} ||= 'normal';
    if (arch() !~ /ppc/) {
	$in->ask_from_({ messages => N("Bootloader main options"),
			 interactive_help_id => 'setupBootloader',
		       }, [
            { label => N("Bootloader to use"), val => \$b->{method}, list => [ keys %$method_choices ], format => sub { $method_choices->{$_[0]} } },
                arch() =~ /sparc/ ? (
            { label => N("Bootloader installation"), val => \$b->{use_partition}, list => [ 0, 1 ], format => sub { $silo_install_lang[$_[0]] } },
		) : if_(arch() !~ /ia64/,
            { label => N("Boot device"), val => \$b->{boot}, list => [ map { "/dev/$_" } (map { $_->{device} } (@{$all_hds->{hds}}, grep { !isFat_or_NTFS($_) } @$fstab)), detect_devices::floppies_dev() ], not_edit => !$::expert },
            { label => N("Compact"), val => \$b->{compact}, type => "bool", text => N("compact"), advanced => 1 },
            { label => N("Video mode"), val => \$b->{vga}, list => [ keys %bootloader::vga_modes ], not_edit => !$::expert, format => sub { $bootloader::vga_modes{$_[0]} }, advanced => 1 },
		),
            { label => N("Delay before booting default image"), val => \$b->{timeout} },
            { label => N("Enable ACPI"), val => \$force_acpi, type => 'bool' },
            { label => N("Force No APIC"), val => \$force_noapic, type => 'bool' },
		if_($security >= 4 || $b->{password} || $b->{restricted},
            { label => N("Password"), val => \$b->{password}, hidden => 1 },
            { label => N("Password (again)"), val => \$b->{password2}, hidden => 1 },
            { label => N("Restrict command line options"), val => \$b->{restricted}, type => "bool", text => N("restrict") },
		),
                if_(arch() !~ /alpha/ && arch() !~ /ppc/,
            { label => N("Create a bootdisk"), val => \$mkbootdisk, type => 'bool', advanced => 1 },
		),
            { label => N("Clean /tmp at each boot"), val => \$clean_tmp, type => 'bool', advanced => 1 },
            { label => N("Precise RAM size if needed (found %d MB)", availableRamMB()), val => \$memsize, advanced => 1 },
		if_(detect_devices::isLaptop(),
            { label => N("Enable multiple profiles"), val => \$profiles, type => 'bool', advanced => 1 },
		),
        ],
        complete => sub {
	    !$memsize || $memsize =~ /K$/ || $memsize =~ s/^(\d+)M?$/$1M/i or $in->ask_warn('', N("Give the ram size in MB")), return 1;
	    #-				     $security > 4 && length($b->{password}) < 6 and $in->ask_warn('', N("At this level of security, a password (and a good one) in lilo is requested")), return 1;
	    $b->{restricted} && !$b->{password} and $in->ask_warn('', N("Option ``Restrict command line options'' is of no use without a password")), return 1;
	    $b->{password} eq $b->{password2} or !$b->{restricted} or $in->ask_warn('', [ N("The passwords do not match"), N("Please try again") ]), return 1;
	    0;
	}) or return 0;
    } else {
	$b->{boot} = $partition_table::mac::bootstrap_part;	
	$in->ask_from_({ messages => N("Bootloader main options"),
			 interactive_help_id => 'setupYabootGeneral',
		       }, [
            { label => N("Bootloader to use"), val => \$b->{method}, list => [ keys %$method_choices ], format => sub { $method_choices->{$_[0]} } },
            { label => N("Init Message"), val => \$b->{'init-message'} },
            { label => N("Boot device"), val => \$b->{boot}, list => [ map { "/dev/$_" } (map { $_->{device} } (grep { isAppleBootstrap($_) } @$fstab)) ], not_edit => !$::expert },
            { label => N("Open Firmware Delay"), val => \$b->{delay} },
            { label => N("Kernel Boot Timeout"), val => \$b->{timeout} },
            { label => N("Enable CD Boot?"), val => \$b->{enablecdboot}, type => "bool" },
            { label => N("Enable OF Boot?"), val => \$b->{enableofboot}, type => "bool" },
            { label => N("Default OS?"), val => \$b->{defaultos}, list => [ 'linux', 'macos', 'macosx', 'darwin' ] },
        ]) or return 0;				
    }

    #- remove bios mapping if the user changed the boot device
    delete $b->{bios} if $b->{boot} ne $prev_boot;

    if ($b->{method} eq 'grub') {
	$in->do_pkgs->ensure_is_installed('grub', "/usr/sbin/grub", 1) or return 0;
    }

    bootloader::set_profiles($b, $profiles);
    bootloader::set_append($b, "mem", $memsize);
    if ($prev_force_acpi != $force_acpi) {
	bootloader::set_append($b, acpi => ($force_acpi ? '' : 'ht'));
    }
    if ($prev_force_noapic != $force_noapic) {
	($force_noapic ? \&bootloader::set_append : \&bootloader::remove_append_simple)->($b, 'noapic');
    }

    if ($prev_clean_tmp != $clean_tmp) {
	if ($clean_tmp && !fsedit::has_mntpoint('/tmp', $all_hds)) {
	    push @{$all_hds->{special}}, { device => 'none', mntpoint => '/tmp', type => 'tmpfs' };
	} else {
	    @{$all_hds->{special}} = grep { $_->{mntpoint} eq '/tmp' } @{$all_hds->{special}};
	}
    }
    mkbootdisk($in, $b, $fstab) or return &setupBootloader__general if $mkbootdisk;

    1;
}

sub setupBootloader__entries {
    my ($in, $b, $all_hds, $fstab) = @_;

    my $Modify = sub {
	my ($e) = @_;
	my $default = my $old_default = $e->{label} eq $b->{default};

	my @l;
	if ($e->{type} eq "image") { 
	    @l = (
{ label => N("Image"), val => \$e->{kernel_or_dev}, list => [ map { s/$::prefix//; $_ } glob_("$::prefix/boot/vmlinuz*") ], not_edit => 0 },
{ label => N("Root"), val => \$e->{root}, list => [ map { "/dev/$_->{device}" } @$fstab ], not_edit => !$::expert },
{ label => N("Append"), val => \$e->{append} },
  if_(arch() !~ /ppc|ia64/,
{ label => N("Video mode"), val => \$e->{vga}, list => [ keys %bootloader::vga_modes ], format => sub { $bootloader::vga_modes{$_[0]} }, not_edit => !$::expert },
),
{ label => N("Initrd"), val => \$e->{initrd}, list => [ map { s/$::prefix//; $_ } glob_("$::prefix/boot/initrd*") ], not_edit => 0 },
{ label => N("Read-write"), val => \$e->{'read-write'}, type => 'bool' }
	    );
	    @l = @l[0..2] unless $::expert;
	} else {
	    @l = ( 
{ label => N("Root"), val => \$e->{kernel_or_dev}, list => [ map { "/dev/$_->{device}" } @$fstab, detect_devices::floppies_dev() ], not_edit => !$::expert },
if_(arch() !~ /sparc|ppc|ia64/,
{ label => N("Table"), val => \$e->{table}, list => [ '', map { "/dev/$_->{device}" } @{$all_hds->{hds}} ], not_edit => !$::expert },
{ label => N("Unsafe"), val => \$e->{unsafe}, type => 'bool' }
),
	    );
	    @l = $l[0] unless $::expert;
	}
	if (arch() !~ /ppc/) {
	    @l = (
		  { label => N("Label"), val => \$e->{label} },
		  @l,
		  { label => N("Default"), val => \$default, type => 'bool' },
		 );
	} else {
	    unshift @l, { label => N("Label"), val => \$e->{label}, list => ['macos', 'macosx', 'darwin'] };
	    if ($e->{type} eq "image") {
		@l = ({ label => N("Label"), val => \$e->{label} },
		$::expert ? @l[1..4] : (@l[1..2], { label => N("Append"), val => \$e->{append} }),
		if_($::expert, { label => N("Initrd-size"), val => \$e->{initrdsize}, list => [ '', '4096', '8192', '16384', '24576' ] }),
		if_($::expert, $l[5]),
		{ label => N("NoVideo"), val => \$e->{novideo}, type => 'bool' },
		{ label => N("Default"), val => \$default, type => 'bool' }
		);
	    }
	}

	$in->ask_from_(
	    {
	     interactive_help_id => arch() =~ /ppc/ ? 'setupYabootAddEntry' : 'setupBootloaderAddEntry',
	     callbacks => {
	       complete => sub {
		   $e->{label} or $in->ask_warn('', N("Empty label not allowed")), return 1;
		   $e->{kernel_or_dev} or $in->ask_warn('', $e->{type} eq 'image' ? N("You must specify a kernel image") : N("You must specify a root partition")), return 1;
		   member(lc $e->{label}, map { lc $_->{label} } grep { $_ != $e } @{$b->{entries}}) and $in->ask_warn('', N("This label is already used")), return 1;
		   0;
	       } } }, \@l) or return;

	$b->{default} = $old_default || $default ? $default && $e->{label} : $b->{default};
	bootloader::configure_entry($e); #- hack to make sure initrd file are built.
	1;
    };

    my $Add = sub {
	my @labels = map { $_->{label} } @{$b->{entries}};
	my ($e, $prefix);
	if ($in->ask_from_list_('', N("Which type of entry do you want to add?"),
				[ N_("Linux"), arch() =~ /sparc/ ? N_("Other OS (SunOS...)") : arch() =~ /ppc/ ? 
				  N_("Other OS (MacOS...)") : N_("Other OS (windows...)") ]
			       ) eq "Linux") {
	    $e = { type => 'image',
		   root => '/dev/' . fsedit::get_root($fstab)->{device}, #- assume a good default.
		 };
	    $prefix = "linux";
	} else {
	    $e = { type => 'other' };
	    $prefix = arch() =~ /sparc/ ? "sunos" : arch() =~ /ppc/ ? "macos" : "windows";
	}
	$e->{label} = $prefix;
	for (my $nb = 0; member($e->{label}, @labels); $nb++) {
	    $e->{label} = "$prefix-$nb";
	}
	$Modify->($e) or return;
	push @{$b->{entries}}, $e;
	$e;
    };

    my $Remove = sub {
	my ($e) = @_;
	delete $b->{default} if $b->{default} eq $e->{label};
	@{$b->{entries}} = grep { $_ != $e } @{$b->{entries}};
	1;
    };

    my @prev_entries = @{$b->{entries}};
    if ($in->ask_from__add_modify_remove('',
N("Here are the entries on your boot menu so far.
You can create additional entries or change the existing ones."), [ { 
        format => sub {
	    my ($e) = @_;
	    ref($e) ? 
	      "$e->{label} ($e->{kernel_or_dev})" . ($b->{default} eq $e->{label} && "  *") : 
		translate($e);
	}, list => $b->{entries},
    } ], Add => $Add, Modify => $Modify, Remove => $Remove)) {
	1;
    } else {
	@{$b->{entries}} = @prev_entries;
	'';
    }
}

my @etc_pass_fields = qw(name pw uid gid realname home shell);
sub unpack_passwd {
    my ($l) = @_;
    my %l; @l{@etc_pass_fields} = split ':', chomp_($l);
    \%l;
}
sub pack_passwd {
    my ($l) = @_;
    join(':', @$l{@etc_pass_fields}) . "\n";
}

sub get_autologin() {
    my %desktop = getVarsFromSh("$::prefix/etc/sysconfig/desktop");
    my $desktop = $desktop{DESKTOP} || 'KDE';
    my $autologin = do {
	if (($desktop{DISPLAYMANAGER} || $desktop) eq 'GNOME') {
	    my %conf = read_gnomekderc("$::prefix/etc/X11/gdm/gdm.conf", 'daemon');
	    text2bool($conf{AutomaticLoginEnable}) && $conf{AutomaticLogin};
	} else { # KDM / MdkKDM
	    my %conf = read_gnomekderc("$::prefix/usr/share/config/kdm/kdmrc", 'X-:0-Core');
	    text2bool($conf{AutoLoginEnable}) && $conf{AutoLoginUser};
	}
    };
    { autologin => $autologin, desktop => $desktop };
}

sub set_autologin {
    my ($user, $desktop) = @_;
    my $autologin = bool2text($user);

    #- Configure KDM / MDKKDM
    eval { update_gnomekderc("$::prefix/usr/share/config/kdm/kdmrc", 'X-:0-Core' => (
	AutoLoginEnable => $autologin,
	AutoLoginUser => $user,
    )) };

    #- Configure GDM
    eval { update_gnomekderc("$::prefix/etc/X11/gdm/gdm.conf", daemon => (
	AutomaticLoginEnable => $autologin,
	AutomaticLogin => $user,
    )) };
  
    if ($user) {
	my %l = getVarsFromSh("$::prefix/etc/sysconfig/desktop");
	$l{DESKTOP} = $desktop;
	setVarsInSh("$::prefix/etc/sysconfig/desktop", \%l);
	log::l("cat $::prefix/etc/sysconfig/desktop ($desktop):\n", cat_("$::prefix/etc/sysconfig/desktop"));
    }
    my $xdm_autologin_cfg = "$::prefix/etc/sysconfig/autologin";
    if (member($desktop, 'KDE', 'GNOME')) {
	unlink $xdm_autologin_cfg;
    } else {
	setVarsInShMode($xdm_autologin_cfg, 0644,
			{ USER => $user, AUTOLOGIN => bool2yesno($user), EXEC => '/usr/X11R6/bin/startx.autologin' });
    }
}

sub rotate_log {
    my ($f) = @_;
    if (-e $f) {
	my $i = 1;
	for (; -e "$f$i" || -e "$f$i.gz"; $i++) {}
	rename $f, "$f$i";
    }
}
sub rotate_logs {
    my ($prefix) = @_;
    rotate_log("$prefix/root/drakx/$_") foreach qw(ddebug.log install.log);
}

sub writeandclean_ldsoconf {
    my ($prefix) = @_;
    my $file = "$prefix/etc/ld.so.conf";
    my @l = chomp_(cat_($file));
    @l = grep { !m|^(/usr)?/lib(64)?$| } @l; #- no need to have /lib and /usr/lib in ld.so.conf
    push @l, '/usr/X11R6/lib', if_(arch() =~ /x86_64/, '/usr/X11R6/lib64');
    push @l, grep { -d "$::prefix$_" } '/usr/lib/qt3/lib', if_(arch() =~ /x86_64/, '/usr/lib/qt3/lib64'); #- needed for upgrade where package renaming can cause this to disappear
    output($file, map { "$_\n" } uniq(@l));
}

sub shells() {
    grep { -x "$::prefix$_" } chomp_(cat_("$::prefix/etc/shells"));
}

sub inspect {
    my ($part, $o_prefix, $b_rw) = @_;

    isMountableRW($part) or return;

    my $dir = $::isInstall ? "/tmp/inspect_tmp_dir" : "/root/.inspect_tmp_dir";

    if ($part->{isMounted}) {
	$dir = ($o_prefix || '') . $part->{mntpoint};
    } elsif ($part->{notFormatted} && !$part->{isFormatted}) {
	$dir = '';
    } else {
	mkdir $dir, 0700;
	eval { fs::mount($part->{device}, $dir, type2fs($part, 'skip'), !$b_rw) };
	$@ and return;
    }
    my $h = before_leaving {
	if (!$part->{isMounted} && $dir) {
	    fs::umount($dir);
	    unlink($dir)
	}
    };
    $h->{dir} = $dir;
    $h;
}

sub ask_user_one {
    my ($in, $users, $security, %options) = @_;

    my @icons = facesnames();

    my %high_security_groups = (
        xgrp => N("access to X programs"),
	rpm => N("access to rpm tools"),
	wheel => N("allow \"su\""),
	adm => N("access to administrative files"),
	ntools => N("access to network tools"),
	ctools => N("access to compilation tools"),
    );

    my $u;
    $u->{password2} ||= $u->{password} ||= '';
    $u->{shell} ||= '/bin/bash';
    my $names = @$users ? N("(already added %s)", join(", ", map { $_->{realname} || $_->{name} } @$users)) : '';
    
    my %groups;
    my $verif = sub {
        $u->{password} eq $u->{password2} or $in->ask_warn('', [ N("The passwords do not match"), N("Please try again") ]), return 1,2;
        $security > 3 && length($u->{password}) < 6 and $in->ask_warn('', N("This password is too simple")), return 1,2;
	    $u->{name} or $in->ask_warn('', N("Please give a user name")), return 1,0;
        $u->{name} =~ /^[a-z]+?[a-z0-9_-]*?$/ or $in->ask_warn('', N("The user name must contain only lower cased letters, numbers, `-' and `_'")), return 1,0;
        length($u->{name}) <= 32 or $in->ask_warn('', N("The user name is too long")), return 1,0;
        member($u->{name}, 'root', map { $_->{name} } @$users) and $in->ask_warn('', N("This user name has already been added")), return 1,0;
        return 0;
    };
    my $ret = $in->ask_from_(
        { title => N("Add user"),
          messages => N("Enter a user\n%s", $options{additional_msg} || $names),
          interactive_help_id => 'addUser',
          focus_first => 1,
          if_(!$::isInstall, ok => N("Done")),
          cancel => $options{noaccept} ? '' : N("Accept user"),
          callbacks => {
	          focus_out => sub {
		      if ($_[0] eq '0') {
			  $u->{name} ||= lc first($u->{realname} =~ /([\w-]+)/);
		      }
		  },
	          complete => sub { $u->{name} ? &$verif : 0 },
                  canceled => $verif,
                  ok_disabled => sub { $security >= 4 && !@$users || $options{needauser} && !$u->{name} },
	  } }, [ 
	  { label => N("Real name"), val => \$u->{realname} },
          { label => N("User name"), val => \$u->{name} },
          { label => N("Password"),val => \$u->{password}, hidden => 1 },
          { label => N("Password (again)"), val => \$u->{password2}, hidden => 1 },
          { label => N("Shell"), val => \$u->{shell}, list => [ shells() ], not_edit => !$::expert, advanced => 1 },
	    if_($security <= 3 && !$options{noicons} && @icons,
	  { label => N("Icon"), val => \ ($u->{icon} ||= 'man'), list => \@icons, icon2f => \&face2png, format => \&translate },
	    ),
	    if_($security > 3,
                map {
                    { label => $_, val => \$groups{$_}, text => $high_security_groups{$_}, type => 'bool' }
                } keys %high_security_groups,
               ),
               ],
                            );
    $u->{groups} = [ grep { $groups{$_} } keys %groups ];

    push @$users, $u if $u->{name};

    return $ret;
}

sub ask_users {
    my ($in, $users, $security) = @_;

    while (1) {
        ask_user_one($in, $users, $security) and return;
    }
}

sub sessions() {
    split(' ', run_program::rooted_get_stdout($::prefix, '/usr/sbin/chksession', '-l'));
}

sub autologin {
    my ($o, $in) = @_;

    my @wm = sessions();
    my @users = map { $_->{name} } @{$o->{users} || []};

    if (member('KDE', @wm) && @users == 1 && $o->{meta_class} eq 'desktop') {
	$o->{desktop} = 'KDE';
	$o->{autologin} = $users[0];
    } elsif (@wm > 1 && @users && !$o->{authentication}{NIS} && $o->{security} <= 2) {
	my $use_autologin = @users == 1;

	$in->ask_from_(
		       { title => N("Autologin"),
			 messages => N("I can set up your computer to automatically log on one user.") },
		       [ { label => N("Do you want to use this feature?"), val => \$use_autologin, type => 'bool' },
			 { label => N("Choose the default user:"), val => \$o->{autologin}, list => \@users, disabled => sub { !$use_autologin } },
			 { label => N("Choose the window manager to run:"), val => \$o->{desktop}, list => \@wm, disabled => sub { !$use_autologin } } ]
		      );
	delete $o->{autologin} if !$use_autologin;
    } else {
	delete $o->{autologin};
    }
}

#- part of perl-MDK-Common-1.1.7-2mdk but since cooker is frozen...
sub uniq_(&@) {
    my $f = shift;
    my %l;
    $l{$f->($_)} = 1 foreach @_;
    grep { delete $l{$f->($_)} } @_;
}

sub selectLanguage {
    my ($in, $lang, $o_langs_) = @_;

    my $common = { messages => N("Please choose a language to use."),
		   title => 'language choice',
		   interactive_help_id => 'selectLanguage' };

    if ($::isInstall) {
	my $langs = $o_langs_ || {};
	my $using_images = $in->isa('interactive::gtk') && !$in->{vga16};

	#- to create the default value, use the first location for that value :/
	$lang = if_(!$::move, first(lang::l2location($lang)).'|').$lang;
	
	my %name2l = map { lang::l2name($_) => $_ } lang::list_langs();
	my $listval2val = sub { $_[0] =~ /\|(.*)/ ? $1 : $_[0] };

	my @langs = map { my $l = $_; uniq_ { $_->[0] } map { [ $::move ? $l : "$_|$l", $_, $l ] } lang::l2location($l) } lang::list_langs();
	#- since gtk version will use images (function image2f) we need to sort differently
	my $sort_func = $using_images ? \&lang::l2transliterated : \&lang::l2name;
	@langs = map { $_->[0] } sort { $sort_func->($a->[2]) cmp $sort_func->($b->[2]) } @langs;

	add2hash($common, { cancel => '',
			    advanced_messages => formatAlaTeX(N("Mandrake Linux can support multiple languages. Select
the languages you would like to install. They will be available
when your installation is complete and you restart your system.")),
			    callbacks => { advanced => sub { $langs->{$listval2val->($lang)} = 1 } } });
			    
	$in->ask_from_($common,
	[ { val => \$lang, separator => '|', 
	    if_($using_images, image2f => sub { $name2l{$_[0]} =~ /^[a-z]/ ? ('', "langs/lang-$name2l{$_[0]}") : $_[0] }),
	    format => sub { $_[0] =~ /(.*\|)(.*)/ ? $1.lang::l2name($2) : lang::l2name($_[0]) },
	    list => \@langs, sort => 0 },
	    if_($o_langs_ && !$::move, if_($::isInstall,
			     { val => \$in->{locale}{utf8}, type => 'bool', text => N("Use Unicode by default"), advanced => 1 }),
		{ val => \$langs->{all}, type => 'bool', text => N("All languages"), advanced => 1 },
	        map {
		  { val => \$langs->{$_->[0]}, type => 'bool', disabled => sub { $langs->{all} },
		    text => $_->[1], advanced => 1,
		    image => "langs/lang-$_->[0]",
		  } 
	      } sort { $a->[1] cmp $b->[1] } map { [ $_, $sort_func->($_) ] } lang::list_langs())
	]) or return;
	$langs->{$listval2val->($lang)} = 1;
	$langs->{$_} or delete $langs->{$_} foreach keys %$langs;  #- clean hash
	
	#- convert to the default locale for asked language
	$listval2val->($lang);

    } else {
	my @langs = sort { lang::l2name($a) cmp lang::l2name($b) } lang::list_langs(exclude_non_installed => 1);
	die 'one lang only' if @langs == 1;
	$in->ask_from_($common,
		       [ { val => \$lang, type => 'list',
			   format => sub { lang::l2name($_[0]) }, list => \@langs } ]) or return;
	$lang;
    }
}

sub selectCountry {
    my ($o, $locale) = @_;

    my $country = $locale->{country};
    my @countries = lang::list_countries(exclude_non_installed => !$::isInstall);
    my @best = uniq map { if_((/^\Q$locale->{lang}/ || substr($_, 0, 2) eq substr($locale->{lang}, 0, 2))
			      && /.._(..)/, $1) } @lang::locales;
    @best == 1 and @best = ();

    my ($other, $ext_country);
    member($country, @best) or ($ext_country, $country) = ($country, $ext_country);
    $o->ask_from_(
		  { title => N("Country / Region"), 
		    messages => N("Please choose your country."),
		    interactive_help_id => 'selectCountry',
		    advanced_messages => N("Here is the full list of available countries"),
		    advanced_label => N("More"),
		    advanced_state => $ext_country && scalar(@best),
		    callbacks => { changed => sub { $other = $_[0] == 1 } },
		  },