summaryrefslogtreecommitdiffstats
path: root/perl-install/partition_table.pm
blob: 632085b26005a9334516f78fa25a564be3c2861c (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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
package partition_table; # $Id$

use diagnostics;
use strict;

use common;
use fs::type;
use partition_table::raw;
use detect_devices;
use log;

our @fields2save = qw(primary extended totalsectors isDirty will_tell_kernel);


sub hd2minimal_part {
    my ($hd) = @_;
    { 
	rootDevice => $hd->{device}, 
	if_($hd->{usb_media_type}, is_removable => 1),
    };
}

#- works for both hard drives and partitions ;p
sub description {
    my ($hd) = @_;
    my $win = $hd->{device_windobe};

    sprintf "%s%s (%s)", 
      $hd->{device}, 
      $win && " [$win:]", 
	join(', ', 
	     grep { $_ } 
	       formatXiB($hd->{totalsectors} || $hd->{size}, 512),
	       $hd->{info}, $hd->{mntpoint}, $hd->{fs_type});
}

sub adjustStartAndEnd {
    my ($hd, $part) = @_;

    $hd->adjustStart($part);
    $hd->adjustEnd($part);
}

sub verifyNotOverlap {
    my ($a, $b) = @_;
    $a->{start} + $a->{size} <= $b->{start} || $b->{start} + $b->{size} <= $a->{start};
}
sub verifyInside {
    my ($a, $b) = @_;
    $b->{start} <= $a->{start} && $a->{start} + $a->{size} <= $b->{start} + $b->{size};
}

sub verifyParts_ {
    foreach my $i (@_) {
	foreach (@_) {
	    next if !$i || !$_ || $i == $_ || isWholedisk($i) || isExtended($i); #- avoid testing twice for simplicity :-)
	    if (isWholedisk($_)) {
		verifyInside($i, $_) or
		  cdie sprintf("partition sector #$i->{start} (%s) is not inside whole disk (%s)!",
			       formatXiB($i->{size}, 512), formatXiB($_->{size}, 512));
	    } elsif (isExtended($_)) {
		verifyNotOverlap($i, $_) or
		  log::l(sprintf("warning partition sector #$i->{start} (%s) is overlapping with extended partition!",
				 formatXiB($i->{size}, 512))); #- only warning for this one is acceptable
	    } else {
		verifyNotOverlap($i, $_) or
		  cdie sprintf("partitions sector #$i->{start} (%s) and sector #$_->{start} (%s) are overlapping!",
			       formatXiB($i->{size}, 512), formatXiB($_->{size}, 512));
	    }
	}
    }
}
sub verifyParts {
    my ($hd) = @_;
    verifyParts_(get_normal_parts($hd));
}
sub verifyPrimary {
    my ($pt) = @_;
    $_->{start} > 0 || arch() =~ /^sparc/ || die "partition must NOT start at sector 0" foreach @{$pt->{normal}};
    verifyParts_(@{$pt->{normal}}, $pt->{extended});
}

sub compute_device_name {
    my ($part, $hd) = @_;
    $part->{device} = $hd->{prefix} . $part->{part_number};
}

sub assign_device_numbers {
    my ($hd) = @_;

    my $i = 1;
    my $start = 1; 
    
    #- on PPC we need to assign device numbers to the holes too - big FUN!
    #- not if it's an IBM machine using a DOS partition table though
    if (arch() =~ /ppc/ && detect_devices::get_mac_model() !~ /^IBM/) {
	#- first sort the normal parts
	$hd->{primary}{normal} = [ sort { $a->{start} <=> $b->{start} } @{$hd->{primary}{normal}} ];
    
	#- now loop through them, assigning partition numbers - reserve one for the holes
	foreach (@{$hd->{primary}{normal}}) {
	    if ($_->{start} > $start) {
		log::l("PPC: found a hole on $hd->{prefix} before $_->{start}, skipping device..."); 
		$i++;
	    }
	    $_->{part_number} = $i;
	    compute_device_name($_, $hd);
	    $start = $_->{start} + $_->{size};
	    $i++;
	}
    } else {
	foreach (@{$hd->{primary}{raw}}) {
	    $_->{part_number} = $i;
	    compute_device_name($_, $hd);
	    $i++;
	}
	foreach (map { $_->{normal} } @{$hd->{extended} || []}) {
	    my $dev = $hd->{prefix} . $i;
	    my $renumbered = $_->{device} && $dev ne $_->{device};
	    if ($renumbered) {
		require fs::mount;
		eval { fs::mount::umount_part($_) }; #- at least try to umount it
		will_tell_kernel($hd, del => $_, 'delay_del');
		push @{$hd->{partitionsRenumbered}}, [ $_->{device}, $dev ];
	    }
	    $_->{part_number} = $i;
	    compute_device_name($_, $hd);
	    if ($renumbered) {
		will_tell_kernel($hd, add => $_, 'delay_add');
	    }
	    $i++;
	}
    }

    #- try to figure what the windobe drive letter could be!
    #
    #- first verify there's at least one primary dos partition, otherwise it
    #- means it is a secondary disk and all will be false :(
    #-
    my ($c, @others) = grep { isFat_or_NTFS($_) } @{$hd->{primary}{normal}};

    $i = ord 'C';
    $c->{device_windobe} = chr($i++) if $c;
    $_->{device_windobe} = chr($i++) foreach grep { isFat_or_NTFS($_) } map { $_->{normal} } @{$hd->{extended}};
    $_->{device_windobe} = chr($i++) foreach @others;
}

sub remove_empty_extended {
    my ($hd) = @_;
    my $last = $hd->{primary}{extended} or return;
    @{$hd->{extended}} = grep {
	if ($_->{normal}) {
	    $last = $_;
	} else {
	    %{$last->{extended}} = $_->{extended} ? %{$_->{extended}} : ();
	}
	$_->{normal};
    } @{$hd->{extended}};
    adjust_main_extended($hd);
}

sub adjust_main_extended {
    my ($hd) = @_;

    if (!is_empty_array_ref $hd->{extended}) {
	my ($l, @l) = @{$hd->{extended}};

	# the first is a special case, must recompute its real size
	my $start = round_down($l->{normal}{start} - 1, $hd->{geom}{sectors});
	my $end = $l->{normal}{start} + $l->{normal}{size};
	my $only_linux = 1; my $has_win_lba = 0;
	foreach (map { $_->{normal} } $l, @l) {
	    $start = min($start, $_->{start});
	    $end = max($end, $_->{start} + $_->{size});
	    $only_linux &&= isTrueLocalFS($_) || isSwap($_);
	    $has_win_lba ||= $_->{pt_type} == 0xc || $_->{pt_type} == 0xe;
	}
	$l->{start} = $hd->{primary}{extended}{start} = $start;
	$l->{size} = $hd->{primary}{extended}{size} = $end - $start;
    }
    if (!@{$hd->{extended} || []} && $hd->{primary}{extended}) {
	will_tell_kernel($hd, del => $hd->{primary}{extended});
	%{$hd->{primary}{extended}} = (); #- modify the raw entry
	delete $hd->{primary}{extended};
    }
    verifyParts($hd); #- verify everything is all right
}

sub adjust_local_extended {
    my ($hd, $part) = @_;

    my $extended = find { $_->{normal} == $part } @{$hd->{extended} || []} or return;
    $extended->{size} = $part->{size} + $part->{start} - $extended->{start};

    #- must write it there too because values are not shared
    my $prev = find { $_->{extended}{start} == $extended->{start} } @{$hd->{extended} || []} or return;
    $prev->{extended}{size} = $part->{size} + $part->{start} - $prev->{extended}{start};
}

sub get_normal_parts {
    my ($hd) = @_;

    @{$hd->{primary}{normal} || []}, map { $_->{normal} } @{$hd->{extended} || []};
}

sub get_normal_parts_and_holes {
    my ($hd) = @_;
    my ($start, $last) = ($hd->first_usable_sector, $hd->last_usable_sector);

    ref($hd) or print("get_normal_parts_and_holes: bad hd" . backtrace(), "\n");

    my $minimal_hole = put_in_hash({ pt_type => 0 }, hd2minimal_part($hd));

    my @l = map {
	my $current = $start;
	$start = $_->{start} + $_->{size};
	my $hole = { start => $current, size => $_->{start} - $current, %$minimal_hole };
	put_in_hash($hole, hd2minimal_part($hd));
	$hole, $_;
    } sort { $a->{start} <=> $b->{start} } grep { !isWholedisk($_) } get_normal_parts($hd);

    push @l, { start => $start, size => $last - $start, %$minimal_hole };
    grep { !isEmpty($_) || $_->{size} >= $hd->cylinder_size } @l;
}

sub read_one($$) {
    my ($hd, $sector) = @_;
    my ($pt, $info);

    #- it can be safely considered that the first sector is used to probe the partition table
    #- but other sectors (typically for extended partition ones) have to match this type!
    if (!$sector) {
	my @parttype = (
	  if_(arch() =~ /^ia64/, 'gpt'),
	  arch() =~ /^sparc/ ? ('sun', 'bsd') : ('dos', 'bsd', 'sun', 'mac'),
	);
	foreach ('empty', @parttype, 'unknown') {
	    /unknown/ and die "unknown partition table format on disk " . $hd->{file};
	    eval {
		# perl_checker: require partition_table::bsd
		# perl_checker: require partition_table::dos
		# perl_checker: require partition_table::empty
		# perl_checker: require partition_table::gpt
		# perl_checker: require partition_table::mac
		# perl_checker: require partition_table::sun
		require "partition_table/$_.pm";
		bless $hd, "partition_table::$_";
		($pt, $info) = $hd->read($sector);
		log::l("found a $_ partition table on $hd->{file} at sector $sector");
	    };
	    $@ or last;
	}
    } else {
	#- keep current blessed object for that, this means it is neccessary to read sector 0 before.
	($pt, $info) = $hd->read($sector);
    }

    my @extended = $hd->hasExtended ? grep { isExtended($_) } @$pt : ();
    my @normal = grep { $_->{size} && !isEmpty($_) && !isExtended($_) } @$pt;
    my $nb_special_empty = int(grep { $_->{size} && isEmpty($_) } @$pt);

    @extended > 1 and die "more than one extended partition";

    put_in_hash($_, hd2minimal_part($hd)) foreach @normal, @extended;
    { raw => $pt, extended => $extended[0], normal => \@normal, info => $info, nb_special_empty => $nb_special_empty };
}

sub read {
    my ($hd) = @_;
    my $pt = read_one($hd, 0) or return 0;
    $hd->{primary} = $pt;
    undef $hd->{extended};
    verifyPrimary($pt);
    eval {
	my $need_removing_empty_extended;
	if ($pt->{extended}) {
	    read_extended($hd, $pt->{extended}, \$need_removing_empty_extended) or return 0;
	}
	if ($need_removing_empty_extended) {
	    #- special case when hda5 is empty, it must be skipped
	    #- (windows XP generates such partition tables)
	    remove_empty_extended($hd); #- includes adjust_main_extended
	}
	
    }; 
    die "extended partition: $@" if $@;

    assign_device_numbers($hd);
    remove_empty_extended($hd);

    $hd->set_best_geometry_for_the_partition_table;
    1;
}

sub read_extended {
    my ($hd, $extended, $need_removing_empty_extended) = @_;

    my $pt = read_one($hd, $extended->{start}) or return 0;
    $pt = { %$extended, %$pt };

    push @{$hd->{extended}}, $pt;
    @{$hd->{extended}} > 100 and die "oops, seems like we're looping here :(  (or you have more than 100 extended partitions!)";

    if (@{$pt->{normal}} == 0) {
	$$need_removing_empty_extended = 1;
	delete $pt->{normal};
	print "need_removing_empty_extended\n";
    } elsif (@{$pt->{normal}} > 1) {
	die "more than one normal partition in extended partition";
    } else {
	$pt->{normal} = $pt->{normal}[0];
	#- in case of extended partitions, the start sector is local to the partition or to the first extended_part!
	$pt->{normal}{start} += $pt->{start};

	#- the following verification can broke an existing partition table that is
	#- correctly read by fdisk or cfdisk. maybe the extended partition can be
	#- recomputed to get correct size.
	if (!verifyInside($pt->{normal}, $extended)) {
	    $extended->{size} = $pt->{normal}{start} + $pt->{normal}{size};
	    verifyInside($pt->{normal}, $extended) or die "partition $pt->{normal}{device} is not inside its extended partition";
	}
    }

    if ($pt->{extended}) {
	$pt->{extended}{start} += $hd->{primary}{extended}{start};
	return read_extended($hd, $pt->{extended}, $need_removing_empty_extended);
    } else {
	1;
    }
}

sub will_tell_kernel {
    my ($hd, $action, $o_part, $o_delay) = @_;

    if ($action eq 'resize') {
	will_tell_kernel($hd, del => $o_part);
	will_tell_kernel($hd, add => $o_part);
    } else {
	my $part_number;
	if ($o_part) {
	    ($part_number) = $o_part->{device} =~ /(\d+)$/ or
	      #- do not die, it occurs when we zero_MBR_and_dirty a raw_lvm_PV
	      log::l("ERROR: will_tell_kernel bad device " . description($o_part)), return;
	}

	my @para =
	  $action eq 'force_reboot' ? () :
	  $action eq 'add' ? ($part_number, $o_part->{start}, $o_part->{size}) :
	  $action eq 'del' ? $part_number :
	  internal_error("unknown action $action");

	push @{$hd->{'will_tell_kernel' . ($o_delay || '')} ||= []}, [ $action, @para ];
    }
    if (!$o_delay) {
	foreach my $delay ('delay_del', 'delay_add') {
	    my $l = delete $hd->{"will_tell_kernel$delay"} or next;
	    push @{$hd->{will_tell_kernel} ||= []}, @$l;
	}
    }
    $hd->{isDirty} = 1;
}

sub tell_kernel {
    my ($hd, $tell_kernel) = @_;

    my $F = partition_table::raw::openit($hd);

    my $force_reboot = any { $_->[0] eq 'force_reboot' } @$tell_kernel;
    if (!$force_reboot) {
	foreach (@$tell_kernel) {
	    my ($action, $part_number, $o_start, $o_size) = @$_;
	    
	    if ($action eq 'add') {
		$force_reboot ||= !c::add_partition(fileno $F, $part_number, $o_start, $o_size);
	    } elsif ($action eq 'del') {
		$force_reboot ||= !c::del_partition(fileno $F, $part_number);
	    }
	    log::l("tell kernel $action ($hd->{device} $part_number $o_start $o_size) force_reboot=$force_reboot rebootNeeded=$hd->{rebootNeeded}");
	}
    }
    if ($force_reboot) {
	my @magic_parts = grep { $_->{isMounted} && $_->{real_mntpoint} } get_normal_parts($hd);
	foreach (@magic_parts) {
	    syscall_('umount', $_->{real_mntpoint}) or log::l(N("error unmounting %s: %s", $_->{real_mntpoint}, $!));
	}
	$hd->{rebootNeeded} = !ioctl($F, c::BLKRRPART(), 0);
	log::l("tell kernel force_reboot ($hd->{device}), rebootNeeded=$hd->{rebootNeeded}");

	foreach (@magic_parts) {
	    syscall_('mount', $_->{real_mntpoint}, $_->{fs_type}, c::MS_MGC_VAL()) or log::l(N("mount failed: ") . $!);
	}
    }
}

# write the partition table
sub write {
    my ($hd) = @_;
    $hd->{isDirty} or return;
    $hd->{readonly} and internal_error("a read-only partition table should not be dirty ($hd->{device})!");

    #- set first primary partition active if no primary partitions are marked as active.
    if (my @l = @{$hd->{primary}{raw}}) {
	foreach (@l) { 
	    $_->{local_start} = $_->{start}; 
	    $_->{active} ||= 0;
	}
	$l[0]{active} = 0x80 if !any { $_->{active} } @l;
    }

    #- last chance for verification, this make sure if an error is detected,
    #- it will never be writed back on partition table.
    verifyParts($hd);

    $hd->write(0, $hd->{primary}{raw}, $hd->{primary}{info}) or die "writing of partition table failed";

    #- should be fixed but a extended exist with no real extended partition, that blanks mbr!
    if (arch() !~ /^sparc/) {
	foreach (@{$hd->{extended}}) {
	    # in case of extended partitions, the start sector must be local to the partition
	    $_->{normal}{local_start} = $_->{normal}{start} - $_->{start};
	    $_->{extended} and $_->{extended}{local_start} = $_->{extended}{start} - $hd->{primary}{extended}{start};

	    $hd->write($_->{start}, $_->{raw}) or die "writing of partition table failed";
	}
    }
    $hd->{isDirty} = 0;
    $hd->{hasBeenDirty} = 1; #- used in undo (to know if undo should believe isDirty or not)

    if (my $tell_kernel = delete $hd->{will_tell_kernel}) {
	if (fs::type::is_dmraid($hd)) {
	    fs::dmraid::call_dmraid('-an');
	    fs::dmraid::call_dmraid('-ay');
	} else {
	    tell_kernel($hd, $tell_kernel);
	}
    }
}

sub active {
    my ($hd, $part) = @_;

    $_->{active} = 0 foreach @{$hd->{primary}{normal}};
    $part->{active} = 0x80;
    $hd->{isDirty} = 1;
}


# remove a normal partition from hard drive hd
sub remove {
    my ($hd, $part) = @_;
    my $i;

    #- first search it in the primary partitions
    $i = 0; foreach (@{$hd->{primary}{normal}}) {
	if ($_ eq $part) {
	    will_tell_kernel($hd, del => $_);

	    splice(@{$hd->{primary}{normal}}, $i, 1);
	    %$_ = (); #- blank it

	    $hd->raw_removed($hd->{primary}{raw});
	    return 1;
	}
	$i++;
    }

    my ($first, $second, $third) = map { $_->{normal} } @{$hd->{extended} || []};
    if ($third && $first eq $part) {
	die "Can not handle removing hda5 when hda6 is not the second partition" if $second->{start} > $third->{start};
    }      

    #- otherwise search it in extended partitions
    foreach (@{$hd->{extended} || []}) {
	$_->{normal} eq $part or next;

	delete $_->{normal}; #- remove it
	remove_empty_extended($hd);
	assign_device_numbers($hd);

	will_tell_kernel($hd, del => $part);
	return 1;
    }
    0;
}

# create of partition at starting at `start', of size `size' and of type `pt_type' (nice comment, uh?)
sub add_primary {
    my ($hd, $part) = @_;

    {
	local $hd->{primary}{normal}; #- save it to fake an addition of $part, that way add_primary do not modify $hd if it fails
	push @{$hd->{primary}{normal}}, $part;
	adjust_main_extended($hd); #- verify
	$hd->raw_add($hd->{primary}{raw}, $part);
    }
    push @{$hd->{primary}{normal}}, $part; #- really do it
}

sub add_extended {
    arch() =~ /^sparc|ppc/ and die N("Extended partition not supported on this platform");

    my ($hd, $part, $extended_type) = @_;
    $extended_type =~ s/Extended_?//;

    my $e = $hd->{primary}{extended};

    if ($e && !verifyInside($part, $e)) {
	#-die "sorry, can not add outside the main extended partition" unless $::unsafe;
	my $end = $e->{start} + $e->{size};
	my $start = min($e->{start}, $part->{start});
	$end = max($end, $part->{start} + $part->{size}) - $start;

	{ #- faking a resizing of the main extended partition to test for problems
	    local $e->{start} = $start;
	    local $e->{size} = $end - $start;
	    eval { verifyPrimary($hd->{primary}) };
	    $@ and die
N("You have a hole in your partition table but I can not use it.
The only solution is to move your primary partitions to have the hole next to the extended partitions.");
	}
    }

    if ($e && $part->{start} < $e->{start}) {
	my $l = first(@{$hd->{extended}});

	#- the first is a special case, must recompute its real size
	$l->{start} = round_down($l->{normal}{start} - 1, $hd->cylinder_size);
	$l->{size} = $l->{normal}{start} + $l->{normal}{size} - $l->{start};
	my $ext = { %$l };
	unshift @{$hd->{extended}}, { pt_type => 5, raw => [ $part, $ext, {}, {} ], normal => $part, extended => $ext };
	#- size will be autocalculated :)
    } else {
	my ($ext, $ext_size) = is_empty_array_ref($hd->{extended}) ?
	  ($hd->{primary}, -1) : #- -1 size will be computed by adjust_main_extended
	  (top(@{$hd->{extended}}), $part->{size});
	my %ext = (pt_type => $extended_type || 5, start => $part->{start}, size => $ext_size);

	$hd->raw_add($ext->{raw}, \%ext);
	$ext->{extended} = \%ext;
	push @{$hd->{extended}}, { %ext, raw => [ $part, {}, {}, {} ], normal => $part };
    }
    $part->{start}++; $part->{size}--; #- let it start after the extended partition sector
    adjustStartAndEnd($hd, $part);

    adjust_main_extended($hd);
}

sub add {
    my ($hd, $part, $b_primaryOrExtended, $b_forceNoAdjust) = @_;

    get_normal_parts($hd) >= ($hd->{device} =~ /^rd/ ? 7 : $hd->{device} =~ /^(sd|ida|cciss|ataraid)/ ? 15 : 63) and cdie "maximum number of partitions handled by linux reached";

    set_isFormatted($part, 0);
    put_in_hash($part, hd2minimal_part($hd));
    $part->{start} ||= 1 if arch() !~ /^sparc/; #- starting at sector 0 is not allowed
    adjustStartAndEnd($hd, $part) unless $b_forceNoAdjust;

    my $nb_primaries = $hd->{device} =~ /^rd/ ? 3 : 1;

    if (arch() =~ /^sparc|ppc/ ||
	$b_primaryOrExtended eq 'Primary' ||
	$b_primaryOrExtended !~ /Extended/ && @{$hd->{primary}{normal} || []} < $nb_primaries) {
	eval { add_primary($hd, $part) };
	goto success if !$@;
    }
    if ($hd->hasExtended) {
	eval { add_extended($hd, $part, $b_primaryOrExtended) };
	goto success if !$@;
    }
    {
	add_primary($hd, $part);
    }
  success:
    assign_device_numbers($hd);
    will_tell_kernel($hd, add => $part);
}

# search for the next partition
sub next {
    my ($hd, $part) = @_;

    first(
	  sort { $a->{start} <=> $b->{start} }
	  grep { $_->{start} >= $part->{start} + $part->{size} }
	  get_normal_parts($hd)
	 );
}
sub next_start {
    my ($hd, $part) = @_;
    my $next = &next($hd, $part);
    $next ? $next->{start} : $hd->last_usable_sector;
}

sub load {
    my ($hd, $file, $b_force) = @_;

    my $F = ref $file ? $file : common::open_file($file) || die N("Error reading file %s", $file);

    my $h;
    {
	local $/ = "\0";
	eval <$F>;
    }
    $@ and die N("Restoring from file %s failed: %s", $file, $@);

    ref($h) eq 'ARRAY' or die N("Bad backup file");

    my %h; @h{@fields2save} = @$h;

    $h{totalsectors} == $hd->{totalsectors} or $b_force or cdie "bad totalsectors";

    #- unsure we do not modify totalsectors
    local $hd->{totalsectors};

    @$hd{@fields2save} = @$h;

    delete @$_{qw(isMounted isFormatted notFormatted toFormat toFormatUnsure)} foreach get_normal_parts($hd);
    will_tell_kernel($hd, 'force_reboot'); #- just like undo, do not force write_partitions so that user can see the new partition table but can still discard it
}

sub save {
    my ($hd, $file) = @_;
    my @h = @$hd{@fields2save};
    require Data::Dumper;
    eval { output($file, Data::Dumper->Dump([\@h], ['$h']), "\0") }
      or die N("Error writing to file %s", $file);
}

1;
ass="hl kwd">s/"//g; $b{'init-message'} = $v; } else { $b{$cmd} = $v eq '' ? 1 : $v; } } else { if (($cmd eq 'map-drive' .. $cmd eq 'to') && $cmd eq 'to') { $e->{mapdrive}{$e->{'map-drive'}} = $v; } else { if ($cmd eq 'initrd') { $v = $filter_file->($v); } $e->{$cmd} = $v || 1; } } } sub remove_quotes_and_spaces { local ($_) = @_; s/^\s*//; s/\s*$//; s/^"(.*?)"$/$1/; s/\\"/"/g; s/^\s*//; s/\s*$//; #- do it again for append=" foo" $_; } foreach ('append', 'root', 'default', 'raid-extra-boot') { $b{$_} = remove_quotes_and_spaces($b{$_}) if $b{$_}; } foreach my $entry (@{$b{entries}}) { foreach ('append', 'root', 'label') { $entry->{$_} = remove_quotes_and_spaces($entry->{$_}) if $entry->{$_}; } if ($entry->{kernel_or_dev} =~ /\bmbootpack\b/) { $entry->{initrd} = $entry->{kernel_or_dev}; $entry->{initrd} =~ s/\bmbootpack/initrd/; $entry->{kernel_or_dev} =~ s/\bmbootpack/vmlinuz/; $entry->{kernel_or_dev} =~ s/.img$//; #- assume only xen is configured with mbootpack $entry->{xen} = '/boot/xen.gz'; $entry->{root} = $1 if $entry->{append} =~ s/root=(\S*)\s*//; ($entry->{xen_append}, $entry->{append}) = split '\s*--\s*', $entry->{append}, 2; } } # cleanup duplicate labels (in case file is corrupted) @{$b{entries}} = uniq_ { $_->{label} } @{$b{entries}}; \%b; } sub cleanup_entries { my ($bootloader) = @_; #- cleanup bad entries (in case file is corrupted) @{$bootloader->{entries}} = grep { my $pb = $_->{type} eq 'image' && dirname($_->{kernel_or_dev}) eq '/boot' && ! -e "$::prefix$_->{kernel_or_dev}"; log::l("dropping bootloader entry $_->{label} since $_->{kernel_or_dev} doesn't exist") if $pb; !$pb; } @{$bootloader->{entries}}; } sub suggest_onmbr { my ($hd) = @_; my ($onmbr, $unsafe) = (1, 1); if (my $type = partition_table::raw::typeOfMBR($hd->{device})) { if (member($type, qw(dos dummy empty))) { $unsafe = 0; } elsif (!member($type, qw(lilo grub))) { $onmbr = 0; } log::l("bootloader::suggest_onmbr: type $type, onmbr $onmbr, unsafe $unsafe"); } ($onmbr, $unsafe); } sub allowed_boot_parts { my ($bootloader, $all_hds) = @_; ( @{$all_hds->{hds}}, if_($bootloader->{method} =~ /lilo/, grep { $_->{level} eq '1' } @{$all_hds->{raids}} ), (grep { !isFat_or_NTFS($_) } fs::get::hds_fstab(@{$all_hds->{hds}})), detect_devices::floppies(), ); } sub same_entries { my ($a, $b) = @_; foreach (uniq(keys %$a, keys %$b)) { if (member($_, 'label', 'append', 'mapdrive', 'readonly', 'makeactive')) { next; } else { next if $a->{$_} eq $b->{$_}; my ($inode_a, $inode_b) = map { (stat "$::prefix$_")[1] } ($a->{$_}, $b->{$_}); next if $inode_a && $inode_b && $inode_a == $inode_b; } log::l("entries $a->{label} do not have same $_: $a->{$_} ne $b->{$_}"); return; } 1; } sub add_entry { my ($bootloader, $v) = @_; my $to_add = $v; my $label = $v->{label}; for (my $i = 0; $i < 10;) { my $conflicting = get_label($label, $bootloader); $to_add->{label} = $label; if ($conflicting) { #- replacing $conflicting with $to_add @{$bootloader->{entries}} = map { $_ == $conflicting ? $to_add : $_ } @{$bootloader->{entries}}; #- we will keep $conflicting, but not with same symlinks if used by the entry to add expand_entry_symlinks($bootloader, $conflicting); } else { #- we have found an unused label push @{$bootloader->{entries}}, $to_add; } if (!$conflicting || same_entries($conflicting, $to_add)) { log::l("current labels: " . join(" ", map { $_->{label} } @{$bootloader->{entries}})); return $v; } $to_add = $conflicting; if ($to_add->{label} eq 'linux') { $label = kernel_str2label(vmlinuz2kernel_str($to_add->{kernel_or_dev}), 'use_long_name'); } else { $label =~ s/^alt\d*_//; $label = 'alt' . ($i++ ? $i : '') . "_$label"; } } die 'add_entry'; } sub expand_entry_symlinks { my ($bootloader, $entry) = @_; foreach my $kind ('kernel_or_dev', 'initrd') { my $old_long_name = $bootloader->{old_long_names} && $bootloader->{old_long_names}{$entry->{$kind}} or next; #- replace all the {$kind} using this symlink to the real file log::l("replacing $entry->{$kind} with $old_long_name for bootloader label $entry->{label}"); $entry->{$kind} = $old_long_name; } } sub _do_the_symlink { my ($bootloader, $link, $long_name) = @_; my $existing_link = readlink("$::prefix$link"); if ($existing_link && $existing_link eq $long_name) { #- nothing to do :) return; } if ($existing_link) { #- the symlink is going to change! #- replace all the {$kind} using this symlink to the real file my $old_long_name = $existing_link =~ m!^/! ? $existing_link : "/boot/$existing_link"; if (-e "$::prefix$old_long_name") { $bootloader->{old_long_names}{$link} = $old_long_name; } else { log::l("ERROR: $link points to $old_long_name which does not exist"); } } elsif (-e "$::prefix$link") { log::l("ERROR: $link is not a symbolic link"); } #- changing the symlink symlinkf($long_name, "$::prefix$link") or cp_af("$::prefix/boot/$long_name", "$::prefix$link"); } sub cmp_kernel_versions { my ($va, $vb) = @_; my $rel_a = $va =~ s/-(.*)$// && $1; my $rel_b = $vb =~ s/-(.*)$// && $1; ($va, $vb) = map { [ split /[.-]/ ] } $va, $vb; my $r = 0; mapn_ { $r ||= $_[0] <=> $_[1]; } $va, $vb; $r || $rel_a <=> $rel_b || $rel_a cmp $rel_b; } sub get_mbootpack_filename { my ($entry) = @_; my $mbootpack_file = $entry->{initrd}; $mbootpack_file =~ s/\binitrd/mbootpack/; $entry->{xen} && $mbootpack_file; } sub build_mbootpack { my ($entry) = @_; my $mbootpack = '/usr/bin/mbootpack'; -f $::prefix . $entry->{kernel_or_dev} && -f $::prefix . $entry->{initrd} or return; my $mbootpack_file = get_mbootpack_filename($entry); -f ($::prefix . $mbootpack_file) and return 1; my $error; my $xen_kernel = '/tmp/xen_kernel'; my $xen_vmlinux = '/tmp/xen_vmlinux'; my $_b = before_leaving { unlink $::prefix . $_ foreach $xen_kernel, $xen_vmlinux }; run_program::rooted($::prefix, '/bin/gzip', '>', $xen_kernel, '2>', \$error, '-dc', $entry->{xen}) or die "unable to uncompress xen kernel"; run_program::rooted($::prefix, '/bin/gzip', '>', $xen_vmlinux, '2>', \$error, '-dc', $entry->{kernel_or_dev}) or die "unable to uncompress xen vmlinuz"; run_program::rooted($::prefix, $mbootpack, "2>", \$error, '-o', $mbootpack_file, '-m', $xen_vmlinux, '-m', $entry->{initrd}, $xen_kernel) or die "mbootpack failed: $error"; 1; } sub add_kernel { my ($bootloader, $kernel_str, $v, $b_nolink, $b_no_initrd) = @_; #- eg: for /boot/vmlinuz-2.6.17-13mdvxen0 (pkg kernel-xen0-xxx) #- or /boot/vmlinuz-2.6.18-xen (pkg kernel-xen-uptodate) if ($kernel_str->{version} =~ /xen/ && -f '/boot/xen.gz') { $v->{xen} = '/boot/xen.gz'; } add2hash($v, { type => 'image', label => kernel_str2label($kernel_str), }); #- normalize append and handle special options { my ($simple, $dict) = unpack_append("$bootloader->{perImageAppend} $v->{append}"); if ($v->{label} eq 'failsafe') { #- perImageAppend contains resume=/dev/xxx which we don't want @$dict = grep { $_->[0] ne 'resume' } @$dict; } if (-e "$::prefix/sbin/udev" && cmp_kernel_versions($kernel_str->{version_no_ext}, '2.6.8') >= 0) { log::l("it is a recent kernel, so we remove any existing devfs= kernel option to enable udev"); @$dict = grep { $_->[0] ne 'devfs' } @$dict; } $v->{append} = pack_append($simple, $dict); } #- new versions of yaboot do not handle symlinks $b_nolink ||= arch() =~ /ppc/; $b_nolink ||= $kernel_str->{use_long_name}; my $vmlinuz_long = kernel_str2vmlinuz_long($kernel_str); $v->{kernel_or_dev} = "/boot/$vmlinuz_long"; -e "$::prefix$v->{kernel_or_dev}" or log::l("unable to find kernel image $::prefix$v->{kernel_or_dev}"), return; if (!$b_nolink) { $v->{kernel_or_dev} = '/boot/' . kernel_str2vmlinuz_short($kernel_str); _do_the_symlink($bootloader, $v->{kernel_or_dev}, $vmlinuz_long); } log::l("adding $v->{kernel_or_dev}"); if (!$b_no_initrd) { my $initrd_long = kernel_str2initrd_long($kernel_str); $v->{initrd} = mkinitrd($kernel_str->{version}, $bootloader, $v, "/boot/$initrd_long"); if ($v->{initrd} && !$b_nolink) { $v->{initrd} = '/boot/' . kernel_str2initrd_short($kernel_str); _do_the_symlink($bootloader, $v->{initrd}, $initrd_long); } } add_entry($bootloader, $v); } sub rebuild_initrds { my ($bootloader) = @_; my %done; foreach my $v (grep { $_->{initrd} } @{$bootloader->{entries}}) { my $kernel_str = vmlinuz2kernel_str($v->{kernel_or_dev}); my $initrd_long = '/boot/' . kernel_str2initrd_long($kernel_str); next if $done{$initrd_long}++; rebuild_initrd($kernel_str->{version}, $bootloader, $v, $initrd_long); } } sub duplicate_kernel_entry { my ($bootloader, $new_label) = @_; get_label($new_label, $bootloader) and return; my $entry = { %{ get_label('linux', $bootloader) }, label => $new_label }; add_entry($bootloader, $entry); } my $uniq_dict_appends = join('|', qw(devfs acpi pci resume PROFILE XFree)); sub unpack_append { my ($s) = @_; my @l = "$s " =~ /((?:[^"\s]+|".*?")*)\s+/g; [ grep { !/=/ } @l ], [ map { if_(/(.*?)=(.*)/, [$1, $2]) } @l ]; } sub pack_append { my ($simple, $dict) = @_; #- normalize $simple = [ reverse(uniq(reverse @$simple)) ]; $dict = [ reverse(uniq_ { my ($k, $v) = @$_; $k =~ /^($uniq_dict_appends)$/ ? $k : "$k=$v"; } reverse @$dict) ]; join(' ', @$simple, map { "$_->[0]=$_->[1]" } @$dict); } sub modify_append { my ($b, $f) = @_; my @l = grep { $_->{type} eq 'image' && !($::isStandalone && $_->{label} eq 'failsafe') } @{$b->{entries}}; foreach (\$b->{perImageAppend}, map { \$_->{append} } @l) { my ($simple, $dict) = unpack_append($$_); $f->($simple, $dict); $$_ = pack_append($simple, $dict); log::l("modify_append: $$_"); } } sub append__mem_is_memsize { $_[0] =~ /^\d+[kM]?$/i } sub get_append_simple { my ($b, $key) = @_; my ($simple, $_dict) = unpack_append($b->{perImageAppend}); member($key, @$simple); } sub get_append_with_key { my ($b, $key) = @_; my ($_simple, $dict) = unpack_append($b->{perImageAppend}); my @l = map { $_->[1] } grep { $_->[0] eq $key } @$dict; log::l("more than one $key in $b->{perImageAppend}") if @l > 1; $l[0]; } sub remove_append_simple { my ($b, $key) = @_; modify_append($b, sub { my ($simple, $_dict) = @_; @$simple = grep { $_ ne $key } @$simple; }); } sub set_append_with_key { my ($b, $key, $val) = @_; modify_append($b, sub { my ($_simple, $dict) = @_; if ($val eq '') { @$dict = grep { $_->[0] ne $key } @$dict; } else { push @$dict, [ $key, $val ]; } }); } sub set_append_simple { my ($b, $key) = @_; modify_append($b, sub { my ($simple, $_dict) = @_; @$simple = uniq(@$simple, $key); }); } sub may_append_with_key { my ($b, $key, $val) = @_; set_append_with_key($b, $key, $val) if !get_append_with_key($b, $key); } sub get_append_memsize { my ($b) = @_; my ($_simple, $dict) = unpack_append($b->{perImageAppend}); my $e = find { $_->[0] eq 'mem' && append__mem_is_memsize($_->[1]) } @$dict; $e && $e->[1]; } sub set_append_memsize { my ($b, $memsize) = @_; modify_append($b, sub { my ($_simple, $dict) = @_; @$dict = grep { $_->[0] ne 'mem' || !append__mem_is_memsize($_->[1]) } @$dict; push @$dict, [ mem => $memsize ] if $memsize; }); } sub get_append_netprofile { my ($e) = @_; my ($simple, $dict) = unpack_append($e->{append}); my ($p, $dict_) = partition { $_->[0] eq 'PROFILE' } @$dict; pack_append($simple, $dict_), $p->[0][1]; } sub set_append_netprofile { my ($e, $append, $profile) = @_; my ($simple, $dict) = unpack_append($append); push @$dict, [ 'PROFILE', $profile ] if $profile; $e->{append} = pack_append($simple, $dict); } sub configure_entry { my ($bootloader, $entry) = @_; $entry->{type} eq 'image' or return; if (my $kernel_str = vmlinuz2kernel_str($entry->{kernel_or_dev})) { $entry->{initrd} = mkinitrd($kernel_str->{version}, $bootloader, $entry, $entry->{initrd} || '/boot/' . kernel_str2initrd_short($kernel_str)); } } sub get_kernels_and_labels_before_kernel_remove { my ($to_remove_kernel) = @_; my @kernels = grep { $_ ne $to_remove_kernel } installed_vmlinuz(); map { kernel_str2label($_) => $_ } get_kernel_labels(\@kernels); } sub get_kernels_and_labels { my ($b_prefer_24) = @_; get_kernel_labels([ installed_vmlinuz() ], $b_prefer_24); } sub get_kernel_labels { my ($kernels, $b_prefer_24) = @_; my @kernels_str = sort { cmp_kernel_versions($b->{version_no_ext}, $a->{version_no_ext}) } grep { -d "$::prefix/lib/modules/$_->{version}" } map { vmlinuz2kernel_str($_) } @$kernels; if ($b_prefer_24) { my ($kernel_24, $other) = partition { $_->{ext} eq '' && $_->{version} =~ /^\Q2.4/ } @kernels_str; @kernels_str = (@$kernel_24, @$other); } $kernels_str[0]{ext} = ''; my %labels; foreach (@kernels_str) { if ($labels{$_->{ext}}) { $_->{use_long_name} = 1; } else { $labels{$_->{ext}} = 1; } } @kernels_str; } sub short_ext { my ($kernel_str) = @_; my $short_ext = { 'i586-up-1GB' => 'i586', 'i686-up-4GB' => '4GB', 'xen0' => 'xen', }->{$kernel_str->{ext}}; $short_ext || $kernel_str->{ext}; } sub sanitize_ver { my ($name, $kernel_str) = @_; $name = '' if $name eq 'linux'; my $v = $kernel_str->{version_no_ext}; if ($v =~ s/-\d+\.mm\././) { $name = join(' ', grep { $_ } $name, 'multimedia'); } $v =~ s!md[kv]$!!; $v =~ s!-0\.(pre|rc)(\d+)\.!$1$2-!; my $return = join(' ', grep { $_ } $name, short_ext($kernel_str), $v); length($return) < 30 or $return =~ s!secure!sec!; length($return) < 30 or $return =~ s!enterprise!ent!; length($return) < 30 or $return =~ s!multimedia!mm!; $return; } sub suggest_message_text { my ($bootloader) = @_; if (!$bootloader->{message} && !$bootloader->{message_text} && arch() !~ /ia64/) { my $msg_en = #-PO: these messages will be displayed at boot time in the BIOS, use only ASCII (7bit) N_("Welcome to the operating system chooser! Choose an operating system from the list above or wait for default boot. "); my $msg = translate($msg_en); #- use the english version if more than 40% of 8bits chars #- else, use the translation but force a conversion to ascii #- to be sure there won't be undisplayable characters if (int(grep { $_ & 0x80 } unpack "c*", $msg) / length($msg) > 0.4) { $msg = $msg_en; } else { $msg = Locale::gettext::iconv($msg, "utf-8", "ascii//TRANSLIT"); } $bootloader->{message_text} = $msg; } } sub suggest { my ($bootloader, $all_hds, %options) = @_; my $fstab = [ fs::get::fstab($all_hds) ]; my $root_part = fs::get::root($fstab); my $root = isLoopback($root_part) ? '/dev/loop7' : fs::wild_device::from_part('', $root_part); my $boot = fs::get::root($fstab, 'boot')->{device}; #- PPC xfs module requires enlarged initrd my $xfsroot = $root_part->{fs_type} eq 'xfs'; my ($onmbr, $unsafe) = $bootloader->{crushMbr} ? (1, 0) : suggest_onmbr($all_hds->{hds}[0]); add2hash_($bootloader, arch() =~ /ppc/ ? { defaultos => "linux", entries => [], 'init-message' => "Welcome to Mandriva Linux!", delay => 30, #- OpenFirmware delay timeout => 50, enableofboot => 1, enablecdboot => 1, if_(detect_devices::get_mac_model() =~ /IBM/, boot => "/dev/sda1", ), xfsroot => $xfsroot, } : { bootUnsafe => $unsafe, entries => [], timeout => $onmbr && 10, nowarn => 1, if_(arch() !~ /ia64/, boot => "/dev/" . ($onmbr ? $all_hds->{hds}[0]{device} : $boot), map => "/boot/map", compact => 1, color => 'black/cyan yellow/cyan', 'menu-scheme' => 'wb:bw:wb:bw' ), }); suggest_message_text($bootloader); add2hash_($bootloader, { memsize => $1 }) if cat_("/proc/cmdline") =~ /\bmem=(\d+[KkMm]?)(?:\s.*)?$/; if (my ($s, $port, $speed) = cat_("/proc/cmdline") =~ /console=(ttyS(\d),(\d+)\S*)/) { log::l("serial console $s $port $speed"); set_append_with_key($bootloader, console => $s); any::set_login_serial_console($port, $speed); } my @kernels = get_kernels_and_labels() or die "no kernel installed"; foreach my $kernel (@kernels) { my $e = add_kernel($bootloader, $kernel, { root => $root, if_($options{vga_fb} && $kernel->{ext} eq '', vga => $options{vga_fb}), #- using framebuffer if_($options{vga_fb} && $options{quiet}, append => "splash=silent"), }); if ($options{vga_fb} && $e->{label} eq 'linux') { add_kernel($bootloader, $kernel, { root => $root, label => 'linux-nonfb' }); } } #- remove existing failsafe, do not care if the previous one was modified by the user? @{$bootloader->{entries}} = grep { $_->{label} ne 'failsafe' } @{$bootloader->{entries}}; add_kernel($bootloader, $kernels[0], { root => $root, label => 'failsafe', append => 'failsafe' }); if (arch() =~ /ppc/) { #- if we identified a MacOS partition earlier - add it if (defined $partition_table::mac::macos_part) { add_entry($bootloader, { type => "macos", kernel_or_dev => $partition_table::mac::macos_part }); } } elsif (arch() !~ /ia64/) { #- search for dos (or windows) boot partition. Do not look in extended partitions! my @windows_boot_parts = grep { isFat_or_NTFS($_) && member(fs::type::fs_type_from_magic($_), 'vfat', 'ntfs') } map { @{$_->{primary}{normal}} } @{$all_hds->{hds}}; each_index { add_entry($bootloader, { type => 'other', kernel_or_dev => "/dev/$_->{device}", label => 'windows' . ($::i || ''), table => "/dev/$_->{rootDevice}", makeactive => 1, }); } @windows_boot_parts; } my @preferred = map { "linux-$_" } 'p3-smp-64GB', 'secure', 'enterprise', 'smp', 'i686-up-4GB'; if (my $preferred = find { get_label($_, $bootloader) } @preferred) { $bootloader->{default} ||= $preferred; } $bootloader->{default} ||= "linux"; $bootloader->{method} ||= first(method_choices($all_hds, 1)); } sub detect_main_method { my ($all_hds) = @_; my $bootloader = &read($all_hds); $bootloader && main_method($bootloader->{method}); } sub main_method { my ($method) = @_; $method =~ /(\w+)/ && $1; } sub config_files() { my %files = ( lilo => '/etc/lilo.conf', grub => '/boot/grub/menu.lst', grub_install => '/boot/grub/install.sh', ); map_each { my $content = cat_("$::prefix/$::b"); { main_method => main_method($::a), name => $::a, file => $::b, content => $content }; } %files; } sub method2text { my ($method) = @_; +{ 'lilo-menu' => N("LILO with text menu"), 'grub-graphic' => N("GRUB with graphical menu"), 'grub-menu' => N("GRUB with text menu"), 'yaboot' => N("Yaboot"), 'silo' => N("SILO"), }->{$method}; } sub method_choices_raw { my ($b_prefix_mounted) = @_; detect_devices::is_xbox() ? 'cromwell' : arch() =~ /ppc/ ? 'yaboot' : arch() =~ /ia64/ ? 'lilo' : arch() =~ /sparc/ ? 'silo' : ( if_(!$b_prefix_mounted || whereis_binary('grub', $::prefix), 'grub-graphic', 'grub-menu'), if_(!$b_prefix_mounted || whereis_binary('lilo', $::prefix), 'lilo-menu'), ); } sub method_choices { my ($all_hds, $b_prefix_mounted) = @_; my $fstab = [ fs::get::fstab($all_hds) ]; my $root_part = fs::get::root($fstab); my $have_dmraid = find { fs::type::is_dmraid($_) } @{$all_hds->{hds}}; grep { (!/lilo/ || !isLoopback($root_part) && !$have_dmraid) && (!/grub/ || !isRAID($root_part)) && (!/grub-graphic/ || cat_("/proc/cmdline") !~ /console=ttyS/); } method_choices_raw($b_prefix_mounted); } sub main_method_choices { my ($b_prefix_mounted) = @_; uniq(map { main_method($_) } method_choices_raw($b_prefix_mounted)); } sub configured_main_methods() { my @bad_main_methods = map { if_(!$_->{content}, $_->{main_method}) } config_files(); difference2([ main_method_choices(1) ], \@bad_main_methods); } sub keytable { my ($f) = @_; $f or return; if ($f !~ /\.klt$/) { my $file = "/boot/$f.klt"; run_program::rooted($::prefix, "keytab-lilo.pl", ">", $file, $f) or return; $f = $file; } -r "$::prefix/$f" && $f; } sub create_link_source() { #- we simply do it for all kernels :) #- so this can be used in %post of kernel and also of kernel-source foreach (all("$::prefix/usr/src")) { my ($version) = /^linux-(\d+\.\d+.*)/ or next; foreach (glob("$::prefix/lib/modules/$version*")) { -d $_ or next; log::l("creating symlink $_/build"); symlink "/usr/src/linux-$version", "$_/build"; log::l("creating symlink $_/source"); symlink "/usr/src/linux-$version", "$_/source"; } } } sub dev2yaboot { my ($dev) = @_; devices::make("$::prefix$dev"); #- create it in the chroot my $of_dev; run_program::rooted_or_die($::prefix, "/usr/sbin/ofpath", ">", \$of_dev, $dev); chomp($of_dev); log::l("OF Device: $of_dev"); $of_dev; } sub check_enough_space() { my $e = "$::prefix/boot/.enough_space"; output $e, 1; -s $e or die N("not enough room in /boot"); unlink $e; } sub write_yaboot { my ($bootloader, $all_hds) = @_; my $fstab = [ fs::get::fstab($all_hds) ]; my $file2yaboot = sub { my ($part, $file) = fs::get::file2part($fstab, $_[0]); dev2yaboot('/dev/' . $part->{device}) . "," . $file; }; #- do not write yaboot.conf for old-world macs my $mac_type = detect_devices::get_mac_model(); return if $mac_type =~ /Power Macintosh/; $bootloader->{prompt} ||= $bootloader->{timeout}; if ($bootloader->{message_text}) { eval { output("$::prefix/boot/message", $bootloader->{message_text}) } and $bootloader->{message} = '/boot/message'; } my @conf; if (!get_label($bootloader->{default}, $bootloader)) { log::l("default bootloader entry $bootloader->{default} is invalid, choosing another one"); $bootloader->{default} = $bootloader->{entries}[0]{label}; } push @conf, "# yaboot.conf - generated by DrakX/drakboot"; push @conf, "# WARNING: do not forget to run ybin after modifying this file\n"; push @conf, "default=" . make_label_lilo_compatible($bootloader->{default}) if $bootloader->{default}; push @conf, sprintf('init-message="\n%s\n"', $bootloader->{'init-message'}) if $bootloader->{'init-message'}; if ($bootloader->{boot}) { push @conf, "boot=$bootloader->{boot}"; push @conf, "ofboot=" . dev2yaboot($bootloader->{boot}) if $mac_type !~ /IBM/; } else { die "no bootstrap partition defined."; } push @conf, map { "$_=$bootloader->{$_}" } grep { $bootloader->{$_} } (qw(delay timeout), if_($mac_type !~ /IBM/, 'defaultos')); push @conf, "install=/usr/lib/yaboot/yaboot"; if ($mac_type =~ /IBM/) { push @conf, 'nonvram'; } else { push @conf, 'magicboot=/usr/lib/yaboot/ofboot'; push @conf, grep { $bootloader->{$_} } qw(enablecdboot enableofboot); } foreach my $entry (@{$bootloader->{entries}}) { if ($entry->{type} eq "image") { push @conf, "$entry->{type}=" . $file2yaboot->($entry->{kernel_or_dev}); my @entry_conf; push @entry_conf, "label=" . make_label_lilo_compatible($entry->{label}); push @entry_conf, "root=$entry->{root}"; push @entry_conf, "initrd=" . $file2yaboot->($entry->{initrd}) if $entry->{initrd}; #- xfs module on PPC requires larger initrd - say 6MB? push @entry_conf, "initrd-size=6144" if $bootloader->{xfsroot}; push @entry_conf, qq(append=" $entry->{append}") if $entry->{append}; push @entry_conf, grep { $entry->{$_} } qw(read-write read-only); push @conf, map { "\t$_" } @entry_conf; } else { my $of_dev = dev2yaboot($entry->{kernel_or_dev}); push @conf, "$entry->{type}=$of_dev"; } } my $f = "$::prefix/etc/yaboot.conf"; log::l("writing yaboot config to $f"); renamef($f, "$f.old"); output($f, map { "$_\n" } @conf); } sub install_yaboot { my ($bootloader, $all_hds) = @_; log::l("Installing boot loader..."); write_yaboot($bootloader, $all_hds); when_config_changed_yaboot($bootloader); } sub when_config_changed_yaboot { my ($bootloader) = @_; $::testing and return; if (defined $partition_table::mac::new_bootstrap) { run_program::run("hformat", $bootloader->{boot}) or die "hformat failed"; } my $error; run_program::rooted($::prefix, "/usr/sbin/ybin", "2>", \$error) or die "ybin failed: $error"; } sub install_cromwell { my ($_bootloader, $_all_hds) = @_; log::l("XBox/Cromwell - nothing to install..."); } sub write_cromwell { my ($_bootloader, $_all_hds) = @_; log::l("XBox/Cromwell - nothing to write..."); } sub when_config_changed_cromwell { my ($_bootloader) = @_; log::l("XBox/Cromwell - nothing to do..."); } sub simplify_label { my ($label) = @_; length($label) < 31 or $label =~ s/\.//g; $label = substr($label, 0, 31); #- lilo does not handle more than 31 char long labels $label =~ s/ /_/g; #- lilo does not support blank character in image names, labels or aliases $label; } sub make_label_lilo_compatible { my ($label) = @_; '"' . simplify_label($label) . '"'; } sub write_lilo { my ($bootloader, $all_hds) = @_; $bootloader->{prompt} ||= $bootloader->{timeout}; my $file2fullname = sub { my ($file) = @_; if (arch() =~ /ia64/) { my $fstab = [ fs::get::fstab($all_hds) ]; (my $part, $file) = fs::get::file2part($fstab, $file); my %hds = map_index { $_ => "hd$::i" } map { $_->{device} } sort { my ($a_is_fat, $b_is_fat) = ($a->{fs_type} eq 'vfat', $b->{fs_type} eq 'vfat'); $a_is_fat <=> $b_is_fat || $a->{device} cmp $b->{device}; } @$fstab; $hds{$part->{device}} . ":" . $file; } else { $file; } }; my $quotes = sub { my ($s) = @_; $s =~ s/"/\\"/g; qq("$s"); }; my $quotes_if_needed = sub { my ($s) = @_; $s =~ /["=\s]/ ? $quotes->($s) : $s; }; my @sorted_hds = sort_hds_according_to_bios($bootloader, $all_hds); if (is_empty_hash_ref($bootloader->{bios} ||= {}) && $all_hds->{hds}[0] != $sorted_hds[0]) { log::l("Since we're booting on $sorted_hds[0]{device}, make it bios=0x80"); $bootloader->{bios} = { "/dev/$sorted_hds[0]{device}" => '0x80' }; } my @conf; #- normalize: RESTRICTED is only valid if PASSWORD is set delete $bootloader->{restricted} if !$bootloader->{password}; foreach my $entry (@{$bootloader->{entries}}) { delete $entry->{restricted} if !$entry->{password} && !$bootloader->{password}; } if (get_append_with_key($bootloader, 'console') =~ /ttyS(.*)/) { $bootloader->{serial} ||= $1; } if (!get_label($bootloader->{default}, $bootloader)) { log::l("default bootloader entry $bootloader->{default} is invalid, choosing another one"); $bootloader->{default} = $bootloader->{entries}[0]{label}; } push @conf, "# File generated by DrakX/drakboot"; push @conf, "# WARNING: do not forget to run lilo after modifying this file\n"; push @conf, "default=" . make_label_lilo_compatible($bootloader->{default}) if $bootloader->{default}; push @conf, map { $_ . '=' . $quotes_if_needed->($bootloader->{$_}) } grep { $bootloader->{$_} } qw(boot root map install serial vga keytable raid-extra-boot menu-scheme vmdefault); push @conf, grep { $bootloader->{$_} } qw(linear geometric compact prompt nowarn restricted static-bios-codes); push @conf, "append=" . $quotes->($bootloader->{append}) if $bootloader->{append}; push @conf, "password=" . $bootloader->{password} if $bootloader->{password}; #- also done by msec push @conf, "timeout=" . round(10 * $bootloader->{timeout}) if $bootloader->{timeout}; push @conf, "message=$bootloader->{message}" if $bootloader->{message}; push @conf, "ignore-table" if any { $_->{unsafe} && $_->{table} } @{$bootloader->{entries}}; push @conf, map_each { "disk=$::a bios=$::b" } %{$bootloader->{bios}}; foreach my $entry (@{$bootloader->{entries}}) { my $mbootpack_file = get_mbootpack_filename($entry); if ($mbootpack_file && !build_mbootpack($entry)) { warn "mbootpack is required for xen but unavailable, skipping\n"; next; } push @conf, "$entry->{type}=" . $file2fullname->($mbootpack_file || $entry->{kernel_or_dev}); my @entry_conf; push @entry_conf, "label=" . make_label_lilo_compatible($entry->{label}) if $entry->{label}; if ($entry->{type} eq "image") { push @entry_conf, 'root=' . $quotes_if_needed->($entry->{root}) if $entry->{root} && !$entry->{xen}; push @entry_conf, "initrd=" . $file2fullname->($entry->{initrd}) if $entry->{initrd} && !$mbootpack_file; my $append = join(' ', if_($entry->{xen_append}, $entry->{xen_append}), if_($entry->{xen}, '--', 'root=' . $entry->{root}), if_($entry->{append}, $entry->{append})); push @entry_conf, "append=" . $quotes->($append) if $append; push @entry_conf, "vga=$entry->{vga}" if $entry->{vga}; push @entry_conf, grep { $entry->{$_} } qw(read-write read-only optional); } else { delete $entry->{unsafe} if $entry->{table}; #- we can't have both push @entry_conf, map { "$_=$entry->{$_}" } grep { $entry->{$_} } qw(table boot-as); push @entry_conf, grep { $entry->{$_} } qw(unsafe master-boot); if ($entry->{table}) { #- hum, things like table=c: are needed for some os2 cases, #- in that case $hd below is undef my $hd = fs::get::device2part($entry->{table}, $all_hds->{hds}); if ($hd && $hd != $sorted_hds[0]) { #- boot off the nth drive, so reverse the BIOS maps my $nb = sprintf("0x%x", 0x80 + (find_index { $hd == $_ } @sorted_hds)); $entry->{mapdrive} ||= { '0x80' => $nb, $nb => '0x80' }; } } if ($entry->{mapdrive}) { push @entry_conf, map_each { "map-drive=$::a", " to=$::b" } %{$entry->{mapdrive}}; } } push @entry_conf, "password=$entry->{password}" if $entry->{password}; push @entry_conf, grep { $entry->{$_} } qw(restricted vmwarn vmdisable); push @conf, map { "\t$_" } @entry_conf; } my $f = arch() =~ /ia64/ ? "$::prefix/boot/efi/elilo.conf" : "$::prefix/etc/lilo.conf"; log::l("writing lilo config to $f"); renamef($f, "$f.old"); output_with_perm($f, $bootloader->{password} ? 0600 : 0644, map { "$_\n" } @conf); } sub install_lilo { my ($bootloader, $all_hds) = @_; if (my ($install) = $bootloader->{method} =~ /lilo-(text|menu)/) { $bootloader->{install} = $install; } else { delete $bootloader->{install}; } if ($bootloader->{message_text}) { output("$::prefix/boot/message-text", $bootloader->{message_text}); } my $message = "message-text"; if (-r "$::prefix/boot/$message") { symlinkf $message, "$::prefix/boot/message"; $bootloader->{message} = '/boot/message'; } #- ensure message does not contain the old graphic format if ($bootloader->{message} && -s "$::prefix$bootloader->{message}" > 65_000) { output("$::prefix$bootloader->{message}", ''); } write_lilo($bootloader, $all_hds); when_config_changed_lilo($bootloader); configure_kdm_BootManager('Lilo'); } sub install_raw_lilo { my ($o_force_answer) = @_; my $error; my $answer = $o_force_answer || ''; run_program::rooted($::prefix, "echo $answer | lilo", '2>', \$error) or die "lilo failed: $error"; } sub when_config_changed_lilo { my ($bootloader) = @_; if (!$::testing && arch() !~ /ia64/ && $bootloader->{method} =~ /lilo/) { log::l("Installing boot loader on $bootloader->{boot}..."); install_raw_lilo($bootloader->{force_lilo_answer}); } } #- NB: ide is lower than scsi, this is important for sort_hds_according_to_bios() sub hd2bios_kind { my ($hd) = @_; lc(join('_', $hd->{bus}, $hd->{host})); } sub mixed_kind_of_disks { my ($hds) = @_; (uniq_ { hd2bios_kind($_) } @$hds) > 1; } sub sort_hds_according_to_bios { my ($bootloader, $all_hds) = @_; my $boot_hd = fs::get::device2part($bootloader->{first_hd_device} || $bootloader->{boot}, $all_hds->{hds}); #- $boot_hd is undefined when installing on floppy my $boot_kind = $boot_hd && hd2bios_kind($boot_hd); my $translate = sub { my ($hd) = @_; my $kind = hd2bios_kind($hd); $boot_hd ? ($hd == $boot_hd ? 0 : $kind eq $boot_kind ? 1 : 2) . "_$kind" : $kind; }; sort { $translate->($a) cmp $translate->($b) } @{$all_hds->{hds}}; } sub device_string2grub { my ($dev, $legacy_floppies, $sorted_hds) = @_; if (my $device = fs::get::device2part($dev, [ @$sorted_hds, fs::get::hds_fstab(@$sorted_hds) ])) { device2grub($device, $sorted_hds); } elsif (my $floppy = fs::get::device2part($dev, $legacy_floppies)) { my $bios = find_index { $floppy eq $_ } @$legacy_floppies; "(fd$bios)"; } else { internal_error("unknown device $dev"); } } sub device2grub { my ($device, $sorted_hds) = @_; my ($hd, $part_nb) = $device->{rootDevice} ? (fs::get::device2part($device->{rootDevice}, $sorted_hds), $device->{device} =~ /(\d+)$/) : $device; my $bios = eval { find_index { $hd eq $_ } @$sorted_hds }; if (defined $bios) { my $part_string = defined $part_nb ? ',' . ($part_nb - 1) : ''; "(hd$bios$part_string)"; } else { undef; } } sub read_grub_device_map() { my %grub2dev = map { m!\((.*)\)\s+/dev/(.*)$! } cat_("$::prefix/boot/grub/device.map"); \%grub2dev; } sub write_grub_device_map { my ($legacy_floppies, $sorted_hds) = @_; my $f = "$::prefix/boot/grub/device.map"; renamef($f, "$f.old"); output($f, (map_index { "(fd$::i) /dev/$_->{device}\n" } @$legacy_floppies), (map_index { "(hd$::i) /dev/$_->{device}\n" } @$sorted_hds)); } sub grub2dev_and_file { my ($grub_file, $grub2dev, $o_block_device) = @_; my ($grub_dev, $rel_file) = $grub_file =~ m!\((.*?)\)/?(.*)! or return; my ($hd, $part) = split(',', $grub_dev); $grub2dev->{$hd} or internal_error("$hd has no mapping in device.map (when translating $grub_file)"); $part = $o_block_device ? '' : defined $part && $part + 1; #- grub wants "(hdX,Y)" where lilo just want "hdY+1" my $device = '/dev/' . $grub2dev->{$hd} . $part; $device, $rel_file; } sub grub2dev { my ($grub_file, $grub2dev, $o_block_device) = @_; first(grub2dev_and_file($grub_file, $grub2dev, $o_block_device)); } # replace dummy "(hdX,Y)" in "(hdX,Y)/boot/vmlinuz..." by appropriate path if needed sub grub2file { my ($grub_file, $grub2dev, $fstab) = @_; if (my ($device, $rel_file) = grub2dev_and_file($grub_file, $grub2dev)) { if (my $part = fs::get::device2part($device, $fstab)) { my $mntpoint = $part->{mntpoint} || ''; ($mntpoint eq '/' ? '' : $mntpoint) . '/' . $rel_file; } else { log::l("ERROR: unknown device $device (computed from $grub_file)"); $grub_file; } } else { $grub_file; } } sub boot_copies_dir() { '/boot/copied' } sub create_copy_in_boot { my ($file) = @_; my $s = $file; $s =~ s!/!_!g; my $file2 = boot_copies_dir() . "/$s"; log::l("$file is not available at boot time, creating a copy ($file2)"); mkdir_p(boot_copies_dir()); output("$file2.link", $file . "\n"); update_copy_in_boot("$file2.link"); $file2; } sub update_copy_in_boot { my ($link) = @_; my $orig = chomp_(cat_("$::prefix$link")); (my $dest = $link) =~ s/\.link$// or internal_error("update_copy_in_boot: $link"); if (-e "$::prefix$orig") { log::l("updating $dest from $orig"); cp_af("$::prefix$orig", "$::prefix$dest"); } else { log::l("removing $dest since $orig does not exist anymore"); unlink "$::prefix$link", "$::prefix$orig"; } } sub write_grub { my ($bootloader, $all_hds) = @_; my $fstab = [ fs::get::fstab($all_hds) ]; my @legacy_floppies = detect_devices::floppies(); my @sorted_hds = sort_hds_according_to_bios($bootloader, $all_hds); write_grub_device_map(\@legacy_floppies, \@sorted_hds); my $file2grub; $file2grub = sub { my ($file) = @_; if ($file =~ m!^\(.*\)/!) { $file; #- it's already in grub format } else { my ($part, $rel_file) = fs::get::file2part($fstab, $file, 'keep_simple_symlinks'); if (my $grub = device2grub($part, \@sorted_hds)) { $grub . $rel_file; } elsif (!begins_with($file, '/boot/')) { log::l("$file is on device $part->{device} which is not available at boot time. Copying it"); $file2grub->(create_copy_in_boot($file)); } else { log::l("ERROR: $file is on device $part->{device} which is not available at boot time. Defaulting to a dumb value"); "(hd0,0)$file"; } } }; if (get_append_with_key($bootloader, 'console') =~ /ttyS(\d),(\d+)/) { $bootloader->{serial} ||= "--unit=$1 --speed=$2"; $bootloader->{terminal} ||= "--timeout=" . ($bootloader->{timeout} || 0) . " console serial"; } elsif ($bootloader->{method} eq 'grub-graphic') { my $bin = '/usr/sbin/grub-gfxmenu'; if ($bootloader->{gfxmenu} eq '' && -x "$::prefix/usr/sbin/grub-gfxmenu") { my $locale = $::o->{locale} || do { require lang; lang::read() }; run_program::rooted($::prefix, $bin, '--lang', $locale->{lang}, '--update-gfxmenu'); $bootloader->{gfxmenu} ||= '/boot/gfxmenu'; } #- not handled anymore delete $bootloader->{$_} foreach qw(splashimage viewport shade); } else { delete $bootloader->{gfxmenu}; } { my @conf; push @conf, map { "$_ $bootloader->{$_}" } grep { $bootloader->{$_} } qw(timeout color password serial shade terminal viewport background foreground); push @conf, map { $_ . ' ' . $file2grub->($bootloader->{$_}) } grep { $bootloader->{$_} } qw(gfxmenu); eval { push @conf, "default " . (find_index { $_->{label} eq $bootloader->{default} } @{$bootloader->{entries}}); }; foreach my $entry (@{$bootloader->{entries}}) { my $title = "\ntitle $entry->{label}"; if ($entry->{type} eq "image") { push @conf, $title; push @conf, grep { $entry->{$_} } 'lock'; push @conf, join(' ', 'kernel', $file2grub->($entry->{xen}), $entry->{xen_append}) if $entry->{xen}; my $vga = $entry->{vga} || $bootloader->{vga}; push @conf, join(' ', $entry->{xen} ? 'module' : 'kernel', $file2grub->($entry->{kernel_or_dev}), $entry->{xen} ? '' : 'BOOT_IMAGE=' . simplify_label($entry->{label}), if_($entry->{root}, $entry->{root} =~ /loop7/ ? "root=707" : "root=$entry->{root}"), #- special to workaround bug in kernel (see #ifdef CONFIG_BLK_DEV_LOOP) $entry->{append}, if_($entry->{'read-write'}, 'rw'), if_($vga && $vga ne "normal", "vga=$vga")); push @conf, "module " . $_ foreach @{$entry->{modules} || []}; push @conf, join(' ', $entry->{xen} ? 'module' : 'initrd', $file2grub->($entry->{initrd})) if $entry->{initrd}; } else { my $dev = eval { device_string2grub($entry->{kernel_or_dev}, \@legacy_floppies, \@sorted_hds) }; if (!$dev) { log::l("dropping bad entry $entry->{label} for unknown device $entry->{kernel_or_dev}"); next; } push @conf, $title; push @conf, join(' ', $entry->{grub_noverify} ? 'rootnoverify' : 'root', $dev); if ($entry->{table}) { if (my $hd = fs::get::device2part($entry->{table}, \@sorted_hds)) { if (my $bios = find_index { $hd eq $_ } @sorted_hds) { #- boot off the nth drive, so reverse the BIOS maps my $nb = sprintf("0x%x", 0x80 + $bios); $entry->{mapdrive} ||= { '0x80' => $nb, $nb => '0x80' }; } } } if ($entry->{mapdrive}) { push @conf, map_each { "map ($::b) ($::a)" } %{$entry->{mapdrive}}; } push @conf, "makeactive" if $entry->{makeactive}; push @conf, "chainloader +1"; } } my $f = "$::prefix/boot/grub/menu.lst"; log::l("writing grub config to $f"); renamef($f, "$f.old"); output($f, map { "$_\n" } @conf); } { my $f = "$::prefix/boot/grub/install.sh"; my $boot_dev = device_string2grub($bootloader->{boot}, \@legacy_floppies, \@sorted_hds); my $files_dev = device2grub(fs::get::root_($fstab, 'boot'), \@sorted_hds); renamef($f, "$f.old"); output_with_perm($f, 0755, "grub --device-map=/boot/grub/device.map --batch <<EOF root $files_dev setup --stage2=/boot/grub/stage2 $boot_dev quit EOF "); } check_enough_space(); } sub configure_kdm_BootManager { my ($name) = @_; eval { common::update_gnomekderc_no_create("$::prefix/etc/kde/kdm/kdmrc", 'Shutdown' => ( BootManager => $name )) }; } sub install_grub { my ($bootloader, $all_hds) = @_; write_grub($bootloader, $all_hds); if (!$::testing) { my @files = grep { /(stage1|stage2|_stage1_5)$/ } glob("$::prefix/lib/grub/*/*"); cp_af(@files, "$::prefix/boot/grub"); install_raw_grub(); } configure_kdm_BootManager('Grub'); } sub install_raw_grub() { log::l("Installing boot loader..."); my $error; run_program::rooted($::prefix, "sh", "2>", \$error, '/boot/grub/install.sh') or die "grub failed: $error"; } sub when_config_changed_grub { my ($_bootloader) = @_; #- do not do anything update_copy_in_boot($_) foreach glob($::prefix . boot_copies_dir() . '/*.link'); } sub action { my ($bootloader, $action, @para) = @_; my $main_method = main_method($bootloader->{method}); my $f = $bootloader::{$action . '_' . $main_method} or die "unknown bootloader method $bootloader->{method} ($action)"; $f->($bootloader, @para); } sub install { my ($bootloader, $all_hds) = @_; if (my $part = fs::get::device2part($bootloader->{boot}, [ fs::get::fstab($all_hds) ])) { die N("You can not install the bootloader on a %s partition\n", $part->{fs_type}) if $part->{fs_type} eq 'xfs'; } $bootloader->{keytable} = keytable($bootloader->{keytable}); action($bootloader, 'install', $all_hds); } sub ensure_pkg_is_installed { my ($do_pkgs, $bootloader) = @_; my $main_method = bootloader::main_method($bootloader->{method}); if ($main_method eq 'grub' || $main_method eq 'lilo') { $do_pkgs->ensure_binary_is_installed($main_method, $main_method, 1) or return 0; if ($bootloader->{method} eq 'grub-graphic') { $do_pkgs->ensure_is_installed('mandriva-gfxboot-theme', '/usr/share/gfxboot/themes/Mandriva/boot/message', 1) or return 0; } } 1; } sub update_for_renumbered_partitions { my ($in, $renumbering, $all_hds) = @_; my @configs = grep { $_->{content} } config_files(); $_->{new} = $_->{orig} = $_->{content} foreach @configs; my @sorted_hds; { my $grub2dev = read_grub_device_map(); map_each { $sorted_hds[$1] = fs::get::device2part($::b, $all_hds->{hds}) if $::a =~ /hd(\d+)/; } %$grub2dev; } #- NB: we make the changes with an added string inside so that hda5 is only renamed once to hda6 foreach (@$renumbering) { my ($old, $new) = @$_; log::l("renaming $old -> $new"); (my $lnew = $new) =~ s/(\d+)$/__DRAKX_DONE__$1/; $_->{new} =~ s/\b$old/$lnew/g foreach @configs; any { $_->{name} eq 'grub' } @configs or next; my ($old_grub, $new_grub) = map { device_string2grub($_, [], \@sorted_hds) } $old, $new; log::l("renaming $old_grub -> $new_grub"); (my $lnew_grub = $new_grub) =~ s/\)$/__DRAKX_DONE__)/; $_->{new} =~ s/\Q$old_grub/$lnew_grub/g foreach @configs; } $_->{new} =~ s/__DRAKX_DONE__//g foreach @configs; my @changed_configs = grep { $_->{orig} ne $_->{new} } @configs or return 1; # no need to update $in->ask_okcancel('', N("Your bootloader configuration must be updated because partition has been renumbered")) or return; foreach (@changed_configs) { renamef("$::prefix/$_->{file}", "$::prefix/$_->{file}.old"); output("$::prefix/$_->{file}", $_->{new}); } my $main_method = detect_main_method($all_hds); my @needed = map { $_ eq 'grub' ? 'grub_install' : $_; } $main_method ? $main_method : ('lilo', 'grub'); if (intersection(\@needed, [ map { $_->{name} } @changed_configs ])) { $in->ask_warn('', N("The bootloader can not be installed correctly. You have to boot rescue and choose \"%s\"", N("Re-install Boot Loader"))); } 1; } 1;