package wizards; # $Id: wizards.pm 230802 2007-10-04 13:36:01Z tv $ use strict; use c; use common; =head1 NAME wizards - a layer on top of interactive that ensure proper stepping =head1 SYNOPSIS use wizards; use interactive; my $wiz = wizards->new({ allow_user => "", # do we need root defaultimage => "", # wizard icon init => sub { }, # code run on wizard startup name => "logdrake", # wizard title needed_rpm => "packages list", # packages to install before running the wizard pages => { { name => "welcome", # first step next => "step1", # which step should be displayed after the current one pre => sub { }, # code executing when stepping backward post => sub { }, # code executing when stepping forward; # returned value is next step name (it overrides "next" field) end => , # is it the last step ? default => , # default answer for yes/no or when data does not conatains any fields no_cancel => , # do not display the cancel button (eg for first step) no_back => , # do not display the back button (eg for first step) ignore => , # do not stack this step for back stepping (eg for warnings and the like steps) interactive_help_id => , # help id (for installer only) data => [], # the actual data passed to interactive }, { name => "step1", data => [ { # usual interactive fields: label => N("Banner:"), val => \$o->{var}{wiz_banner}, list => [] , # wizard layer variables: boolean_list => "", # provide default status for booleans list }, ], }, }, }); my $in = 'interactive'->vnew; $wiz->process($in); =head1 DESCRIPTION wizards is a layer built on top of the interactive layer that do proper backward/forward stepping for us. A step is made up of a name/description, a list of interactive fields (see interactive documentation), a "complete", "pre" and "post" callbacks, an help id, ... The "pre" callback is run first. Its only argument is the actual step hash. Then, if the "name" fiels is a code reference, that callback is run and its actual result is used as the description of the step. At this stage, the interactive layer is used to display the actual step. The "post" callback is only run if the user has steped forward. Alternatively, you can call safe_process() rather than process(). safe_process() will handle for you the "wizcancel" exception while running the wizard. Actually, it should be used everywhere but where the wizard is not the main path (eg "mail alert wizard" in logdrake, ...), ie when you may need to do extra exception managment such as destroying the wizard window and the like. =cut sub new { my ($class, $o) = @_; bless $o, $class; } sub check_rpm { my ($in, $rpms) = @_; foreach my $rpm (@$rpms) { next if $in->do_pkgs->is_installed($rpm); if ($in->ask_okcancel(N("Error"), N("%s is not installed\nClick \"Next\" to install or \"Cancel\" to quit", $rpm))) { $::testing and next; if (!$in->do_pkgs->install($rpm)) { local $::Wizard_finished = 1; $in->ask_okcancel(N("Error"), N("Installation failed")); $in->exit; } } else { $in->exit } } } # sync me with interactive::ask_from_normalize() if needed: my %default_callback = (complete => sub { 0 }); sub process { my ($o, $in) = @_; local $::isWizard = 1; local $::Wizard_title = $o->{name} || $::Wizard_title; local $::Wizard_pix_up = $o->{defaultimage} || $::Wizard_pix_up; #require_root_capability() if $> && !$o->{allow_user} && !$::testing; check_rpm($in, $o->{needed_rpm}) if ref($o->{needed_rpm}); if (defined $o->{init}) { my ($res, $msg) = &{$o->{init}}; if (!$res) { $in->ask_okcancel(N("Error"), $msg); die "wizard failed" if !$::testing; } } my @steps; # steps stack # initial step: my $next = 'welcome'; my $page = $o->{pages}{welcome}; while ($next) { local $::Wizard_no_previous = $page->{no_back}; local $::Wizard_no_cancel = $page->{no_cancel} || $page->{end}; local $::Wizard_finished = $page->{end}; defined $page->{pre} and $page->{pre}($page); die qq(inexistant "$next" wizard step) if is_empty_hash_ref($page); # FIXME or the displaying fails my $data = defined $page->{data} ? (ref($page->{data}) eq 'CODE' ? $page->{data}->() : $page->{data}) : []; my $data2; foreach my $d (@$data) { $d->{val} = ${$d->{val_ref}} if $d->{val_ref}; $d->{list} = $d->{list_ref} if $d->{list_ref}; #$d->{val} = ref($d->{val}) eq 'CODE' ? $d->{val}->() : $d->{val}; if ($d->{boolean_list}) { my $i; foreach (@{$d->{boolean_list}}) { push @$data2, { text => $_, type => 'bool', val => \${$d->{val}}->[$i], disabled => $d->{disabled} }; $i++; } } else { push @$data2, $d; } } my $name = ref($page->{name}) ? $page->{name}->() : $page->{name}; my %yesno = (yes => N("Yes"), no => N("No")); my $yes = ref($page->{default}) eq 'CODE' ? $page->{default}->() : $page->{default}; $data2 = [ { val => \$yes, type => 'list', list => [ keys %yesno ], format => sub { $yesno{$_[0]} }, gtk => { use_boxradio => 1 } } ] if $page->{type} eq "yesorno"; my $a; if (ref $data2 eq 'ARRAY' && @$data2) { $a = $in->ask_from_({ title => $o->{name}, messages => $name, (map { $_ => $page->{$_} || $default_callback{$_} } qw(complete)), if_($page->{interactive_help_id}, interactive_help_id => $page->{interactive_help_id}), }, $data2); } else { $a = $in->ask_okcancel($o->{name}, $name, $yes || 'ok'); } # interactive->ask_yesorno does not support stepping forward or backward: $a = $yes if $a && $page->{type} eq "yesorno"; if ($a) { # step forward: push @steps, $next if !$page->{ignore} && $steps[-1] ne $next; my $current = $next; $next = defined $page->{post} ? $page->{post}($page->{type} eq "yesorno" ? $yes eq 'yes' : $a) : 0; return if $page->{end}; if (!$next) { if (!defined $o->{pages}{$next}) { $next = $page->{next}; } else { die qq(the "$next" page (from previous wizard step) is undefined) if !$next; } } die qq(Step "$current": inexistant "$next" page) if !exists $o->{pages}{$next}; } else { # step back: $next = pop @steps; } $page = $o->{pages}{$next}; } } sub safe_process { my ($o, $in) = @_; eval { $o->process($in) }; my $err = $@; if ($err =~ /wizcancel/) { $in->exit(0); } else { die $err if $err; } } 1; href='#n51'>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 189 190 191 192 193 194 195 196 197 198
package partition_table::raw; # $Id$

use diagnostics;
use strict;

use common;
use devices;
use detect_devices;
use log;
use c;

my @MBR_signatures = (
if_(arch() =~ /ppc/,
    map { [ 'yaboot', 0, "PM", 0x200 * $_ + 0x10, "bootstrap\0" ] } 0 .. 61
),
    [ 'empty', 0, "\0\0\0\0" ],
    [ 'grub', 0, "\xEBG", 0x17d, "stage1 \0" ],
    [ 'grub', 0, "\xEBH", 0x17e, "stage1 \0" ],
    [ 'grub', 0, "\xEBH", 0x18a, "stage1 \0" ],
    [ 'grub', 0, "\xEBH", 0x181, "GRUB \0" ],
    [ 'lilo', 0x2,  "LILO" ],
    [ 'lilo', 0x6,  "LILO" ],
    [ 'grub', 0x6,  "GRUB" ],
    [ 'osbs', 0x2,  "OSBS" ], #- http://www.prz.tu-berlin.de/~wolf/os-bs.html
    [ 'pqmagic', 0xef, "PQV" ],
    [ 'BootStar', 0x130, "BootStar:" ],
    [ 'DocsBoot', 0x148, 'DocsBoot' ],
    [ 'system_commander', 0x1ad, "SYSCMNDRSYS" ],
    [ 'Be Os', 0x24, 'Boot Manager' ],
    [ 'os2', 0, "\xFA\xB8\x30\x00", 0xfA, "OS/2" ],
    [ 'TimO', 0, 'IBM Thinkpad hibernation partition' ],
    [ 'dos', 0xa0, "\x25\x03\x4E\x02\xCD\x13" ],
    [ 'dos', 0xa0, "\x00\xB4\x08\xCD\x13\x72" ], #- nt2k's
    [ 'dos', 0x60, "\xBB\x00\x7C\xB8\x01\x02\x57\xCD\x13\x5F\x73\x0C\x33\xC0\xCD\x13" ], #- nt's
    [ 'dos', 0x70, "\x0C\x33\xC0\xCD\x13\x4F\x75\xED\xBE\xA3" ],
    [ 'freebsd', 0xC0, "\x00\x30\xE4\xCD\x16\xCD\x19\xBB\x07\x00\xB4" ],
    [ 'freebsd', 0x160, "\x6A\x10\x89\xE6\x48\x80\xCC\x40\xCD\x13" ],
    [ 'dummy', 0xAC, "\x0E\xB3\x07\x56\xCD\x10\x5E\xEB" ], #- caldera?
    [ 'ranish', 0x100, "\x6A\x10\xB4\x42\x8B\xF4\xCD\x13\x8B\xE5\x73" ],
    [ 'os2', 0x1c2, "\x0A" ],
    [ 'Acronis', 0, "\xE8\x12\x01" ],
);

sub typeOfMBR($) { typeFromMagic(devices::make($_[0]), @MBR_signatures) }
sub typeOfMBR_($) { typeFromMagic($_[0], @MBR_signatures) }

sub hasExtended { 0 }

sub cylinder_size($) {
    my ($hd) = @_;
    $hd->{geom}{sectors} * $hd->{geom}{heads};
}
sub first_usable_sector { 1 }
sub last_usable_sector { 
    my ($hd) = @_;
    $hd->{totalsectors};
}

#- default method for starting a partition, only head size or twice
#- is allowed for starting a partition after a cylinder boundarie.
sub adjustStart($$) {
    my ($hd, $part) = @_;
    my $end = $part->{start} + $part->{size};

    $part->{start} = round_up($part->{start},
			      $part->{start} % cylinder_size($hd) < 2 * $hd->{geom}{sectors} ?
   			      $hd->{geom}{sectors} : cylinder_size($hd));
    $part->{size} = $end - $part->{start};
    $part->{size} > 0 or die "adjustStart get a too small partition to handle correctly";
}
#- adjusting end to match a cylinder boundary, two methods are used and must
#- match at the end, else something is wrong and nothing will be done on
#- partition table.
#- $end2 is computed by removing 2 (or only 1 if only 2 heads on drive) groups
#- of sectors, this is necessary to handle extended partition where logical
#- partition start after 1 (or 2 accepted) groups of sectors (typically 63).
#- $end is floating (is not on cylinder boudary) so we have to choice a good
#- candidate, $end1 or $end2 should always be good except $end1 for small
#- partition size.
sub adjustEnd($$) {
    my ($hd, $part) = @_;
    my $end = $part->{start} + $part->{size};
    $end > $hd->{geom}{cylinders} * cylinder_size($hd) && $end <= $hd->{totalsectors} and return;
    my $end1 = round_down($end, cylinder_size($hd));
    my $end2 = round_up($end - ($hd->{geom}{heads} > 2 ? 2 : 1) * $hd->{geom}{sectors}, cylinder_size($hd));
    $end2 <= $hd->{geom}{cylinders} * cylinder_size($hd) or die "adjustEnd go beyond end of device geometry ($end2 > $hd->{totalsectors})";
    $part->{size} = ($end1 - $part->{start} > cylinder_size($hd) ? $end1 : $end2) - $part->{start};
    $part->{size} > 0 or internal_error("adjustEnd get a too small partition to handle correctly");
}

sub get_geometry($) {
    my ($dev) = @_;
    my $g = "";

    sysopen(my $F, $dev, 0) or return;
    ioctl($F, c::HDIO_GETGEO(), $g) or return;
    my %geom; @geom{qw(heads sectors cylinders start)} = unpack "CCSL", $g;
    $geom{totalcylinders} = $geom{cylinders};

    my $total;
    #- $geom{cylinders} is no good (only a ushort, that means less than 2^16 => at best 512MB)
    if ($total = c::total_sectors(fileno $F)) {
	$geom{cylinders} = int $total / $geom{heads} / $geom{sectors};
    } else {
	$total = $geom{heads} * $geom{sectors} * $geom{cylinders}
    }

    { geom => \%geom, totalsectors => $total };
}

sub openit { 
    my ($hd, $mode) = @_;
    my $F; sysopen($F, $hd->{file}, $mode || 0) and $F;
}

# cause kernel to re-read partition table
sub kernel_read($) {
    my ($hd) = @_;
    common::sync();
    my $F = openit($hd) or return 0;
    common::sync(); sleep(1);
    $hd->{rebootNeeded} = !ioctl($F, c::BLKRRPART(), 0);
    common::sync();
    close $F;
    common::sync(); sleep(1);
}

sub raw_removed {
    my ($_hd, $_raw) = @_;
}
sub can_raw_add {
    my ($hd) = @_;
    $_->{size} || $_->{type} or return 1 foreach @{$hd->{primary}{raw}};
    0;
}
sub raw_add {
    my ($_hd, $raw, $part) = @_;

    foreach (@$raw) {
	$_->{size} || $_->{type} and next;
	$_ = $part;
	return;
    }
    die "raw_add: partition table already full";
}

sub zero_MBR {
    my ($hd) = @_;
    #- force the standard partition type for the architecture
    my $type = arch() =~ /ia64/ ? 'gpt' : arch() eq "alpha" ? "bsd" : arch() =~ /^sparc/ ? "sun" : arch() eq "ppc" ? "mac" : "dos";
    #- override standard mac type on PPC for IBM machines to dos
    $type = "dos" if arch() =~ /ppc/ && detect_devices::get_mac_model() =~ /^IBM/;
    require "partition_table/$type.pm";
    bless $hd, "partition_table::$type";
    $hd->{primary} = $hd->clear_raw();
    delete $hd->{extended};
}

sub zero_MBR_and_dirty {
    my ($hd) = @_;
    zero_MBR($hd);
    $hd->{isDirty} = $hd->{needKernelReread} = 1;

}

#- ugly stuff needed mainly for Western Digital IDE drives
#- try writing what we've just read, yells if it fails
#- testing on last sector of head #0 (unused in 99% cases)
#-
#- return false if the device can't be written to (especially for Smartmedia)
sub test_for_bad_drives {
    my ($hd) = @_;

    log::l("test_for_bad_drives($hd->{file})");
    my $sector = $hd->{geom}{sectors} - 1;
    
    sub error { die "$_[0] error: $_[1]" }

    my $F = openit($hd, 2) or error(openit($hd) ? 'write' : 'read', "can't open device");

    my $seek = sub {
	c::lseek_sector(fileno($F), $sector, 0) or error('read', "seeking to sector $sector failed");
    };
    my $tmp;

    &$seek; sysread $F, $tmp, $SECTORSIZE or error('read', "can't even read ($!)");
    return if $hd->{readonly} || $::testing;
    &$seek; syswrite $F, $tmp or error('write', "can't even write ($!)");

    my $tmp2;
    &$seek; sysread $F, $tmp2, $SECTORSIZE or die "test_for_bad_drives: can't even read again ($!)";
    $tmp eq $tmp2 or die
\N("Something bad is happening on your drive. 
A test to check the integrity of data has failed. 
It means writing anything on the disk will end up with random, corrupted data.");
}

1;