summaryrefslogtreecommitdiffstats
path: root/perl-install/standalone
diff options
context:
space:
mode:
authorSebastien Dupont <sdupont@mandriva.com>2001-11-12 17:44:55 +0000
committerSebastien Dupont <sdupont@mandriva.com>2001-11-12 17:44:55 +0000
commit8b4882a2d80354b74c6f5e51a38f7fd06bd46855 (patch)
treecec24a072d03271d3a5cbb5ffc4ea3722818fe70 /perl-install/standalone
parent477139b1a28f2542dc06d97a6bfe3e4652fcb59c (diff)
downloaddrakx-8b4882a2d80354b74c6f5e51a38f7fd06bd46855.tar
drakx-8b4882a2d80354b74c6f5e51a38f7fd06bd46855.tar.gz
drakx-8b4882a2d80354b74c6f5e51a38f7fd06bd46855.tar.bz2
drakx-8b4882a2d80354b74c6f5e51a38f7fd06bd46855.tar.xz
drakx-8b4882a2d80354b74c6f5e51a38f7fd06bd46855.zip
new backup tool.
backend_mode with options. begin interactive mode.
Diffstat (limited to 'perl-install/standalone')
-rwxr-xr-xperl-install/standalone/drakbackup219
1 files changed, 219 insertions, 0 deletions
diff --git a/perl-install/standalone/drakbackup b/perl-install/standalone/drakbackup
new file mode 100755
index 000000000..78d98e932
--- /dev/null
+++ b/perl-install/standalone/drakbackup
@@ -0,0 +1,219 @@
+#!/usr/bin/perl
+#
+# Copyright (C) 2001 by Sebastien DUPONT <sdupont@mandrakesoft.com>
+# Redistribution of this file is permitted under the terms of the GNU
+# Public License (GPL)
+## description:
+#
+# Drakbacup is use to backup system files and user files
+# Drakbacup allow to restore the system (etc, var files)
+# from starup or on drakconf utility.
+#
+#backup name format: all the time from the /
+# backup_sys_12102001.tar.gz -> default system backup
+# backup_sys_etc_var_proc_12102001.tar.gz -> specific system directories
+# backup_user_james_12102001.tar.gz -> default user backup
+# backup_user_james_documents_12102001.tar.gz -> specific user directories
+# backup_other_proc_12102001.tar.gz -> specific other directories
+#
+# backup name rules: system: begin by sys
+# user: begin by user
+# other: begin by other
+# end of all `date`.tar.gz
+#
+# seems to be good idea to have a txt file where user explain the differences
+# between all the backup
+#
+# save only the differences...
+# find / -mtime -1 \! -type d -print > /tmp/liste.jour
+#
+# build iso fs with rescue.
+#
+# configuration file on /etc/drakconf/drakbackup/drakbakup.conf
+#
+
+use Gtk;
+use lib qw(/usr/lib/libDrakX);
+use interactive;
+use standalone;
+use my_gtk qw(:helpers :wrappers);
+use common;
+use strict;
+
+
+if ("@ARGV" =~ /--help|-h/) {
+ print q(Backup and monitoring application
+
+--list : list of files or directories to backup.
+--default : save default directories.
+--build_cd : build restore iso with the currents backups files
+ & rescue options.
+--build_floppy : build restore floppy.
+--update : don t replace the old backup, only update it.
+--replace : delete backup files before build new.
+--save_dir : by default the backup files are saved in
+ in /var/backup directory so write other directory
+ to change to change it.
+--conf_file : to read other configuration file.
+);
+ exit(0);
+}
+
+# Backend Options.
+my $default = 0;
+my $build_cd = 0;
+my $build_floppy = 0;
+my $comp_mode = 0;
+my $mode = 0;
+my $replace = 0;
+my $update = 0;
+my $conf_file = 0;
+my @list_arg = ();
+my @sys_files ;
+my @home_files;
+my @other_files;
+my $save_path;
+my $option_replace = 0;
+my $option_update = 0;
+my $windows = 0;
+my $central_widget;
+my $interactive;
+
+
+# PATH & Global variables.
+my $cfg_file = "/etc/drakconf/drakbackup/drakbackup.conf";
+
+
+
+foreach (@ARGV) {
+ /--default/ and $default = 1, $mode=-1;
+ /--build_cd/ and $build_cd = 1, $mode=-1;
+ /--build_floppy/ and $build_floppy = 1, $mode=-1;
+ /--replace|-r/ and $replace = 1, $mode=-1;
+ /--update|-u/ and $update = 1, $mode=-1;
+ /--conf_file/ and $mode = 0, next;
+# $mode == 0 and push $conf_file, $_;
+ /--list/ and $mode = 1, next;
+ $mode == 1 and push @list_arg, $_;
+}
+
+sub debug {
+ print "SYS_FILES: $_ \n" foreach (@sys_files);
+ print "HOME_FILES: $_ \n" foreach (@home_files);
+ print "OTHER_FILES: $_ \n" foreach (@other_files);
+ print "PATH_TO_SAVE: $save_path \n";
+ print "OPTION_REPLACE: $option_replace \n";
+ print "OPTION_UPDATE: $option_update \n";
+ print "OPTION_COMP: $comp_mode \n";
+}
+
+sub read_conf_file {
+ foreach (cat_("$cfg_file")) {
+ if (/^SYS_FILES/) {
+ chomp;
+ s/^SYS_FILES=//gi;
+ @sys_files = split(' ', $_ );
+ }
+ if (/^HOME_FILES/) {
+ chomp;
+ s/^HOME_FILES=//gi;
+ @home_files = split(' ', $_ );
+ }
+ if (/^OTHER_FILES/) {
+ chomp;
+ s/^OTHER_FILES=//gi;
+ @other_files = split(' ', $_ );
+ }
+ if (/^PATH_TO_SAVE/) {
+ chomp;
+ $save_path = $_;
+ }
+ if (/^OPTION_REPLACE/){
+ $option_replace = 1;
+ $option_update = 0;
+ }
+ if (/^OPTION_UPDATE/){
+ $option_replace = 0;
+ $option_update = 1;
+ }
+ if (/^OPTION_COMP/) {
+ chomp;
+ s/^OPTION_COMP=//gi;
+ /TAR.GZ/ and $comp_mode = 0;
+ /TAR.BZ2/ and $comp_mode = 1;
+ }
+ }
+ debug;
+}
+
+$build_floppy || $build_cd || $default || @list_arg || $conf_file ? backend_mod() : interactive_mode();
+
+sub backend_mod {
+ read_conf_file();
+}
+
+sub build_cd_fct {
+
+}
+
+sub build_floppy_fct {
+
+}
+
+sub build_backup_files {
+
+}
+
+sub interactive_mode {
+ my $font_box;
+ my $font_sel;
+ $interactive = 1;
+
+ init Gtk;
+
+ my $window1 = $::isEmbedded ? new Gtk::Plug ($::XID) : new Gtk::Window -toplevel;
+ $window1->signal_connect (delete_event => sub { Gtk->exit(0) });
+ $window1->set_position(1);
+ $window1->set_title(_("Fonts Importation"));
+ $window1->set_border_width(5);
+
+ gtkadd($window1,
+ gtkpack_(new Gtk::HBox(0,2),
+ 1, gtkpack_(new Gtk::VBox(0,2),
+ 1, new Gtk::VBox(0,0),
+ 1, gtkpack(gtkset_usize($font_box = new Gtk::VBox(0,5),500, 350),
+ $font_sel = new Gtk::FontSelection,
+ ),
+ 1, new Gtk::VBox(0,0)
+ ),
+ 0, gtkpack_(new Gtk::VBox(0,5),
+ 0, _("DrakFont"),
+ 0, gtkadd(gtkset_layout(new Gtk::VButtonBox, -end),
+ gtksignal_connect(new Gtk::Button(_("Windows Importation")), clicked =>
+ sub { ${$central_widget}->destroy(); $windows = 1; license(\&appli_choice)}),
+ gtksignal_connect(new Gtk::Button(_("Advanced Importation")), clicked =>
+ sub { ${$central_widget}->destroy(); $windows = 0; license(\&advanced_install)}),
+ gtksignal_connect(new Gtk::Button(_("Uninstall Fonts")), clicked =>
+ sub { ${$central_widget}->destroy(); uninstall() }),
+ gtksignal_connect(new Gtk::Button(_("Font List")), clicked =>
+ sub { ${$central_widget}->destroy(); create_fontsel()}),
+ ),
+ 1, new Gtk::VBox(0,0),
+ 1, gtkadd(gtkset_layout(new Gtk::VButtonBox, -end),
+ gtksignal_connect(new Gtk::Button(_(" Help ")), clicked => sub {
+ ${$central_widget}->destroy(); help() }),
+ gtksignal_connect(new Gtk::Button(_("Close")), clicked => sub { Gtk->main_quit() }),
+ ),
+ )
+ ),
+ );
+ $central_widget = \$font_sel;
+ $window1->show_all;
+ $window1->realize;
+ $window1->show_all();
+
+ Gtk->main;
+ Gtk->exit(0);
+}
+
+
f='#n488'>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 630 631 632 633 634 635 636 637 638 639 640
package fsedit; # $Id: fsedit.pm 269284 2010-05-24 15:36:50Z pterjan $

use diagnostics;
use strict;
use vars qw(%suggestions);

#-######################################################################################
#- misc imports
#-######################################################################################
use common;
use partition_table;
use partition_table::raw;
use fs::get;
use fs::type;
use fs::loopback;
use fs::proc_partitions;
use detect_devices;
use devices;
use log;
use fs;

# min_hd_size: only suggest this partition if the hd size is bigger than that
%suggestions = (
  N_("simple") => [
    { mntpoint => "/",     size => MB(300), fs_type => defaultFS(), ratio => 10, maxsize => MB(12300) },
    { mntpoint => "swap",  size => MB(128), fs_type => 'swap', ratio => 1,  maxsize => MB(4000) },
    { mntpoint => "/home", size => MB(300), fs_type => defaultFS(), ratio => 8,  min_hd_size => MB(8000) },
  ], N_("with /usr") => [
    { mntpoint => "/",     size => MB(250), fs_type => defaultFS(), ratio => 1, maxsize => MB(8000) },
    { mntpoint => "swap",  size =>  MB(64), fs_type => 'swap', ratio => 1, maxsize => MB(4000) },
    { mntpoint => "/usr",  size => MB(300), fs_type => defaultFS(), ratio => 4, maxsize => MB(8000) },
    { mntpoint => "/home", size => MB(100), fs_type => defaultFS(), ratio => 3, min_hd_size => MB(7000) },
  ], N_("server") => [
    { mntpoint => "/",     size => MB(150), fs_type => defaultFS(), ratio => 1, maxsize => MB(8000) },
    { mntpoint => "swap",  size =>  MB(64), fs_type => 'swap', ratio => 2, maxsize => MB(4000) },
    { mntpoint => "/usr",  size => MB(300), fs_type => defaultFS(), ratio => 4, maxsize => MB(8000) },
    { mntpoint => "/var",  size => MB(200), fs_type => defaultFS(), ratio => 3 },
    { mntpoint => "/home", size => MB(150), fs_type => defaultFS(), ratio => 3, min_hd_size => MB(7000) },
    { mntpoint => "/tmp",  size => MB(150), fs_type => defaultFS(), ratio => 2, maxsize => MB(4000) },
  ],
);
foreach (values %suggestions) {
    if (arch() =~ /ia64/) {
	@$_ = ({ mntpoint => "/boot/efi", size => MB(50), pt_type => 0xef, ratio => 1, maxsize => MB(150) }, @$_);
    }
}

my @suggestions_mntpoints = (
    "/var/ftp", "/var/www", "/boot", '/usr/local', '/opt',
    arch() =~ /sparc/ ? "/mnt/sunos" : arch() =~ /ppc/ ? "/mnt/macos" : "/mnt/windows",
);

#-######################################################################################
#- Functions
#-######################################################################################
sub recompute_loopbacks {
    my ($all_hds) = @_;
    my @fstab = fs::get::fstab($all_hds);
    @{$all_hds->{loopbacks}} = map { isPartOfLoopback($_) ? @{$_->{loopback}} : () } @fstab;
}

sub raids {
    my ($hds) = @_;

    my @parts = fs::get::hds_fstab(@$hds);

    my @l = grep { isRawRAID($_) } @parts or return [];

    log::l("looking for raids in " . join(' ', map { $_->{device} } @l));
    
    require raid;
    raid::detect_during_install(@l) if $::isInstall;
    raid::get_existing(@l);
}

sub dmcrypts {
    my ($all_hds) = @_;

    my @parts = fs::get::fstab($all_hds);

    my @l = grep { fs::type::isRawLUKS($_) } @parts or return;

    log::l("using dm-crypt from " . join(' ', map { $_->{device} } @l));
    
    require fs::dmcrypt;
    fs::dmcrypt::read_crypttab($all_hds);

    fs::dmcrypt::get_existing(@l);
}

sub lvms {
    my ($all_hds) = @_;
    my @pvs = grep { isRawLVM($_) } fs::get::fstab($all_hds) or return;
    scan_pvs(@pvs);
}

sub scan_pvs {
    my (@pvs) = @_;

    log::l("looking for vgs in " . join(' ', map { $_->{device} } @pvs));

    #- otherwise vgscan will not find them
    devices::make($_->{device}) foreach @pvs; 
    require lvm;

    my @lvms;
    foreach (@pvs) {
	my $name = lvm::pv_to_vg($_) or next;
	my $lvm = find { $_->{VG_name} eq $name } @lvms;
	if (!$lvm) {
	    $lvm = new lvm($name);
	    lvm::update_size($lvm);
	    lvm::get_lvs($lvm);
	    push @lvms, $lvm;
	}
	$_->{lvm} = $name;
	push @{$lvm->{disks}}, $_;
    }
    @lvms;
}

sub handle_dmraid {
    my ($drives, $o_in) = @_;

    @$drives > 1 or return;

    devices::make($_->{device}) foreach @$drives;

    require fs::dmraid; 
    eval { fs::dmraid::init() } or log::l("dmraid::init failed"), return;

    my @vgs = fs::dmraid::vgs();
    log::l(sprintf('dmraid: ' . join(' ', map { "$_->{device} [" . join(' ', @{$_->{disks}}) . "]" } @vgs)));

    if ($o_in && @vgs && $::isInstall) {
	@vgs = grep {
	    $o_in->ask_yesorno('', N("BIOS software RAID detected on disks %s. Activate it?", join(' ', @{$_->{disks}})), 1);
	} @vgs or do {
	    fs::dmraid::call_dmraid('-an');
	    return;
	};
    }
    if (!$::isInstall) {
	fs::dmraid::migrate_device_names($_) foreach @vgs;
    }
    log::l("using dmraid on " . join(' ', map { $_->{device} } @vgs));

    my @used_hds = map {
	my $part = fs::get::device2part($_, $drives) or log::l("handle_dmraid: can't find $_ in known drives");
	if_($part, $part);
    } map { @{$_->{disks}} } @vgs;

    @$drives = difference2($drives, \@used_hds);

    push @$drives, @vgs;
}

sub get_hds {
    my ($o_flags, $o_in) = @_;
    my $flags = $o_flags || {};
    $flags->{readonly} && ($flags->{clearall} || $flags->{clear}) and die "conflicting flags readonly and clear/clearall";

    my @drives = detect_devices::hds();

    #- replace drives used in dmraid by the merged name
    handle_dmraid(\@drives, $o_in) if !$flags->{nodmraid};

    foreach my $hd (@drives) {
	$hd->{file} = devices::make($hd->{device});
    }

    @drives = partition_table::raw::get_geometries(@drives);

    my (@hds, @raw_hds);
    foreach my $hd (@drives) {
	$hd->{readonly} = $flags->{readonly};

	eval { partition_table::raw::test_for_bad_drives($hd) if !$flags->{no_bad_drives} };
	if (my $err = $@) {
	    log::l("test_for_bad_drives returned $err");
	    if ($err =~ /write error:/) { 
		log::l("setting $hd->{device} readonly");
		$hd->{readonly} = 1;
	    } elsif ($err =~ /read error:/) {
		next;
	    } else {
		$o_in and $o_in->ask_warn('', $err);
		next;
	    }
	}

	if ($flags->{clearall} || member($hd->{device}, @{$flags->{clear} || []})) {
	    my $lvms = []; #- temporary one, will be re-created later in get_hds()
	    partition_table_initialize($lvms, $hd, $o_in);
	} else {
	    my $handle_die_and_cdie = sub {
		if (my $type = fs::type::type_subpart_from_magic($hd)) {
		    #- non partitioned drive?
		    if (exists $hd->{usb_description} && $type->{fs_type}) {
			#- USB keys
			put_in_hash($hd, $type);
			push @raw_hds, $hd;
			$hd = '';
			1;
		    } else {
			0;
		    }
		} elsif ($hd->{readonly}) {
		    log::l("using /proc/partitions since diskdrake failed :(");
		    fs::proc_partitions::use_($hd);
		    1;
		} else {
		    0;
		}
	    };
	    my $handled;
	    eval {
		catch_cdie {
		    partition_table::read($hd);
		    if (listlength(partition_table::get_normal_parts($hd)) == 0) {
			$handled = 1 if $handle_die_and_cdie->();
		    } elsif ($::isInstall) {
			if (fs::type::is_dmraid($hd)) {
			    if (my $p = find { ! -e "/dev/$_->{device}" } partition_table::get_normal_parts($hd)) {
				#- dmraid should have created the device, so it means we don't agree
				die sprintf(q(bad dmraid (missing partition %s), you may try rebooting install with option "nodmraid"), $p->{device});
			    }
			} else {
			    fs::proc_partitions::compare($hd) if !detect_devices::is_xbox() && arch() ne 'ppc';
			}
		    }
		} sub {
		    my $err = $@;
		    if ($handle_die_and_cdie->()) {
			$handled = 1;
			0; #- do not continue, transform cdie into die
		    } else {
			!$o_in || $o_in->ask_okcancel('', formatError($err));
		    }
		};
	    };
	    if (my $err = $@) {
		if ($handled) {
		    #- already handled in cdie handler above
		} elsif ($handle_die_and_cdie->()) {
		} elsif ($o_in && $o_in->ask_yesorno(N("Error"), 
N("I cannot read the partition table of device %s, it's too corrupted for me :(
I can try to go on, erasing over bad partitions (ALL DATA will be lost!).
The other solution is to not allow DrakX to modify the partition table.
(the error is %s)

Do you agree to lose all the partitions?
", $hd->{device}, formatError($err)))) {
		    partition_table::raw::zero_MBR($hd);
		} else {
		    #- using it readonly
		    log::l("using /proc/partitions since diskdrake failed :(");
		    fs::proc_partitions::use_($hd);
		}
	    }
	    $hd or next;

	    member($_->{device}, @{$flags->{clear} || []}) and partition_table::remove($hd, $_)
	      foreach partition_table::get_normal_parts($hd);
	}

	my @parts = partition_table::get_normal_parts($hd);

	# checking the magic of the filesystem, do not rely on pt_type
	foreach (@parts) {
	    if (my $type = fs::type::type_subpart_from_magic($_)) {
                $type->{pt_type} = $_->{pt_type}; #- keep {pt_type}
                put_in_hash($_, $type); 
	    } else {
		$_->{bad_fs_type_magic} = 1;
	    }
	}

	if ($hd->{usb_media_type}) {
	    $hd->{is_removable} = 1;
	    $_->{is_removable} = 1 foreach @parts;
	}

	push @hds, $hd;
    }

    #- detect raids before LVM allowing LVM on raid
    my $raids = raids(\@hds);
    my $all_hds = { %{ fs::get::empty_all_hds() }, hds => \@hds, raw_hds => \@raw_hds, lvms => [], raids => $raids };

    $all_hds->{lvms} = [ lvms($all_hds) ];

    fs::get_major_minor([ fs::get::fstab($all_hds) ]);

    # must be done after getting major/minor
    $all_hds->{dmcrypts} = [ dmcrypts($all_hds) ];
    # allow lvm on dmcrypt
    $all_hds->{lvms} = [ lvms($all_hds) ];

    $_->{faked_device} = 0 foreach fs::get::fstab($all_hds);

    $all_hds;
}

#- are_same_partitions() do not look at the device name since things may have changed
sub are_same_partitions {
    my ($part1, $part2) = @_;
    foreach ('start', 'size', 'pt_type', 'fs_type', 'rootDevice') {
	$part1->{$_} eq $part2->{$_} or return 0;
    }
    1;
}

sub is_one_big_fat_or_NT {
    my ($hds) = @_;
    @$hds == 1 or return 0;

    my @l = fs::get::hds_fstab(@$hds);
    @l == 1 && isFat_or_NTFS($l[0]) && fs::get::hds_free_space(@$hds) < MB(10);
}


sub computeSize {
    my ($part, $best, $all_hds, $suggestions) = @_;
    my $max = $part->{maxsize} || $part->{size};
    return min($max, $best->{size}) unless $best->{ratio};

    my %free_space;
    $free_space{$_->{rootDevice}} += $_->{size} foreach fs::get::holes($all_hds);

    my @l = my @L = grep {
	my @possible = $_->{hd} ? $_->{hd} : keys %free_space;
	my $size = $_->{size};
	if (my $dev = find { $free_space{$_} >= $size } @possible) {
	    $free_space{$dev} -= $size;
	    1;
	} else { 0 } } @$suggestions;

    my $free_space = $best->{hd} && $free_space{$best->{hd}} || sum(values %free_space);

    my $cylinder_size_maxsize_adjusted;
    my $tot_ratios = 0;
    while (1) {
	my $old_free_space = $free_space;
	my $old_tot_ratios = $tot_ratios;

	$tot_ratios = sum(map { $_->{ratio} } @l);
	last if $tot_ratios == $old_tot_ratios;

	@l = grep { 
	    if ($_->{ratio} && $_->{maxsize} && $tot_ratios &&
		$_->{size} + $_->{ratio} / $tot_ratios * $old_free_space >= $_->{maxsize}) {
		return min($max, $best->{maxsize}) if $best->{mntpoint} eq $_->{mntpoint};
		$free_space -= $_->{maxsize} - $_->{size};
		if (!$cylinder_size_maxsize_adjusted++) {
		    eval { $free_space += fs::get::part2hd($part, $all_hds)->cylinder_size - 1 };
		}
		0;
	    } else {
		$_->{ratio};
	    } 
	} @l;
    }
    my $size = int min($max, $best->{size} + $free_space * ($tot_ratios && $best->{ratio} / $tot_ratios));
    #- verify other entry can fill the hole
    (any { $_->{size} <= $max - $size } @L) ? $size : $max;
}

sub suggest_part {
    my ($part, $all_hds, $o_suggestions) = @_;
    my $suggestions = $o_suggestions || $suggestions{server} || $suggestions{simple};

    #- suggestions now use {fs_type}, but still keep compatibility
    foreach (@$suggestions) {
	fs::type::set_pt_type($_, $_->{pt_type}) if !exists $_->{fs_type};
    }

    my $hd = fs::get::part2hd($part, $all_hds);
    my $hd_size = $hd && $hd->{totalsectors}; # nb: no $hd if $part is /dev/mdX
    my $has_swap = any { isSwap($_) } fs::get::fstab($all_hds);

    my @local_suggestions =
      grep { !$_->{mntpoint} && !$_->{VG_name} || !fs::get::has_mntpoint($_->{mntpoint}, $all_hds) || isSwap($_) && !$has_swap }
      grep { !$_->{min_hd_size} || !$hd_size || $_->{min_hd_size} <= $hd_size }
      grep { !$_->{hd} || $_->{hd} eq $part->{rootDevice} }
	@$suggestions;

    #- this allows specifying the size using a relative size.
    #- one should rather use {ratio} instead
    foreach (@local_suggestions) {
	if ($_->{percent_size} && $_->{percent_size} =~ /(.+?)%?$/) {
	    $_->{size} = $1 / 100 * $hd_size;
	    log::l("in suggestion, setting size=$_->{size} for percent_size=$_->{percent_size}");
	}
    }

    my ($best) =
      grep { !$_->{maxsize} || $part->{size} <= $_->{maxsize} }
      grep { $_->{size} <= ($part->{maxsize} || $part->{size}) }
      grep { !$part->{fs_type} || $part->{fs_type} eq $_->{fs_type} || isTrueFS($part) && isTrueFS($_) }
	@local_suggestions;

    defined $best or return 0; #- sorry no suggestion :(

    $part->{mntpoint} = $best->{mntpoint};
    fs::type::set_type_subpart($part, $best) if !isTrueFS($best) || !isTrueFS($part);
    $part->{size} = computeSize($part, $best, $all_hds, \@local_suggestions);
    foreach ('options', 'lv_name', 'encrypt_key', 'primaryOrExtended',
	     'device_LABEL', 'prefer_device_LABEL', 'device_UUID', 'prefer_device_UUID', 'prefer_device') {
	$part->{$_} = $best->{$_} if $best->{$_};
    }
    $best;
}

sub suggestions_mntpoint {
    my ($all_hds) = @_;
    sort grep { !/swap/ && !fs::get::has_mntpoint($_, $all_hds) }
      (@suggestions_mntpoints, map { $_->{mntpoint} } @{$suggestions{server} || $suggestions{simple}});
}

#- you can do this before modifying $part->{mntpoint}
#- so $part->{mntpoint} should not be used here, use $mntpoint instead
sub check_mntpoint {
    my ($mntpoint, $part, $all_hds) = @_;

    $mntpoint eq '' || isSwap($part) || isNonMountable($part) and return 0;
    $mntpoint =~ m|^/| or die N("Mount points must begin with a leading /");
    $mntpoint =~ m|[\x7f-\xff]| and cdie N("Mount points should contain only alphanumerical characters");
    fs::get::mntpoint2part($mntpoint, [ grep { $_ ne $part } fs::get::really_all_fstab($all_hds) ]) and die N("There is already a partition with mount point %s\n", $mntpoint);

    if ($mntpoint eq "/" && isRAID($part) && !fs::get::has_mntpoint("/boot", $all_hds)) {
	# lilo handles / on RAID1
	if ($part->{level} ne '1') {
	    cdie N("You've selected a software RAID partition as root (/).
No bootloader is able to handle this without a /boot partition.
Please be sure to add a separate /boot partition");
	} else {
	    # LILO only handles 0.90 metadata
	    if ($part->{isFormatted} && $part->{metadata} && $part->{metadata} ne '0.90') {
		cdie N("Metadata version unsupported for a boot partition. Please be sure to add a separate /boot partition.");
	    } else {
		$part->{metadata} = '0.90';
	    }
	}
    }

    if ($mntpoint eq "/boot" && isRAID($part))  {
	die N("You've selected a software RAID partition as /boot.
No bootloader is able to handle this.") if $part->{level} ne '1'; # lilo handles /boot on RAID1
	# LILO only handles 0.90 metadata
	if ($part->{isFormatted} && $part->{metadata} && $part->{metadata} ne '0.90') {
	    die N("Metadata version unsupported for a boot partition.");
	} else {
	    $part->{metadata} = '0.90';
	}
    }

    if ($mntpoint eq "/" && (isLUKS($part) || isRawLUKS($part)) && !fs::get::has_mntpoint("/boot", $all_hds)) {
	cdie N("You've selected an encrypted partition as root (/).
No bootloader is able to handle this without a /boot partition.
Please be sure to add a separate /boot partition");
    }

    if ($mntpoint eq "/boot" && (isLUKS($part) || isRawLUKS($part)))  {
	die N("You cannot use an encrypted filesystem for mount point %s", "/boot");
    }

    #- NB: if the LV doesn't exist, lv_nb_pvs returns 0
    die N("You cannot use the LVM Logical Volume for mount point %s since it spans physical volumes", $mntpoint)
      if $mntpoint eq '/boot' && isLVM($part) && lvm::lv_nb_pvs($part) > 1;
    cdie N("You've selected the LVM Logical Volume as root (/).
The bootloader is not able to handle this when the volume spans physical volumes.
You should create a separate /boot partition first") if $mntpoint eq "/" && isLVM($part) && lvm::lv_nb_pvs($part) != 1 && !fs::get::has_mntpoint("/boot", $all_hds);

    cdie N("This directory should remain within the root filesystem")
      if member($mntpoint, qw(/root));
    die N("This directory should remain within the root filesystem")
      if member($mntpoint, qw(/bin /dev /etc /lib /sbin /mnt /media));
    die N("You need a true filesystem (ext2/3/4, reiserfs, xfs, or jfs) for this mount point\n")
      if !isTrueLocalFS($part) && $mntpoint eq '/';
    die N("You need a true filesystem (ext2/3/4, reiserfs, xfs, or jfs) for this mount point\n")
      if !isTrueFS($part) && member($mntpoint, '/home', fs::type::directories_needed_to_boot());
    die N("You cannot use an encrypted filesystem for mount point %s", $mntpoint)
      if $part->{options} =~ /encrypted/ && member($mntpoint, qw(/ /usr /var /boot));

    local $part->{mntpoint} = $mntpoint;
    fs::loopback::check_circular_mounts($part, $all_hds);
}

sub add {
    my ($hd, $part, $all_hds, $options) = @_;

    isSwap($part) ?
      ($part->{mntpoint} = 'swap') :
      $options->{force} || check_mntpoint($part->{mntpoint}, $part, $all_hds);

    delete $part->{maxsize};

    if (isLVM($hd)) {
	lvm::lv_create($hd, $part);
    } else {
	partition_table::add($hd, $part, $options->{primaryOrExtended});
    }
    fs::get_major_minor([ $part ]);
}

sub allocatePartitions {
    my ($all_hds, $to_add) = @_;

    my @to_add = @$to_add;
 
    foreach my $part_ (fs::get::holes($all_hds, 'non_readonly')) {
	my ($start, $size, $dev) = @$part_{"start", "size", "rootDevice"};
	my ($part, $suggested);
	while ($suggested = suggest_part($part = { start => $start, size => 0, maxsize => $size, rootDevice => $dev }, 
					 $all_hds, \@to_add)) {
	    my $hd = fs::get::part2hd($part, $all_hds);
	    add($hd, $part, $all_hds, { primaryOrExtended => $part->{primaryOrExtended} });
	    $size -= $part->{size} + $part->{start} - $start;
	    $start = $part->{start} + $part->{size};
 	    @to_add = grep { $_ != $suggested } @to_add;
	}
    }
}

sub auto_allocate {
    my ($all_hds, $o_suggestions) = @_;
    my $before = listlength(fs::get::fstab($all_hds));

    my $suggestions = $o_suggestions || $suggestions{simple};
    allocatePartitions($all_hds, $suggestions);

    if ($o_suggestions) {
	auto_allocate_raids($all_hds, $suggestions);
	if (auto_allocate_vgs($all_hds, $suggestions)) {
	    #- allocatePartitions needs to be called twice, once for allocating PVs, once for allocating LVs
	    my @vgs = map { $_->{VG_name} } @{$all_hds->{lvms}};
	    my @suggested_lvs = grep { member($_->{hd}, @vgs) } @$suggestions;
	    allocatePartitions($all_hds, \@suggested_lvs);
	}
    }

    partition_table::assign_device_numbers($_) foreach @{$all_hds->{hds}};

    if ($before == listlength(fs::get::fstab($all_hds))) {
	# find out why auto_allocate failed
	if (any { !fs::get::has_mntpoint($_->{mntpoint}, $all_hds) } @$suggestions) {
	    die N("Not enough free space for auto-allocating");
	} else {
	    die N("Nothing to do");
	}
    }
}

sub auto_allocate_raids {
    my ($all_hds, $suggestions) = @_;

    my @raids = grep { isRawRAID($_) } fs::get::fstab($all_hds) or return;

    require raid;
    my @mds = grep { $_->{hd} =~ /md/ } @$suggestions;
    foreach my $md (@mds) {
	my @raids_ = grep { !$md->{parts} || $md->{parts} =~ /\Q$_->{mntpoint}/ } @raids;
	@raids = difference2(\@raids, \@raids_);

	my %h = %$md;
	delete @h{'hd', 'parts'}; # keeping mntpoint, level, chunk-size, fs_type/pt_type
	$h{disks} = \@raids_;

	my $part = raid::new($all_hds->{raids}, %h);

	raid::updateSize($part);
	push @raids, $part; #- we can build raid over raid
    }
}

sub auto_allocate_vgs {
    my ($all_hds, $suggestions) = @_;

    my @pvs = grep { isRawLVM($_) } fs::get::fstab($all_hds) or return 0;

    my @vgs = grep { $_->{VG_name} } @$suggestions or return 0;

    partition_table::write($_) foreach @{$all_hds->{hds}};

    require lvm;

    foreach my $vg (@vgs) {
	my $lvm = new lvm($vg->{VG_name});
	push @{$all_hds->{lvms}}, $lvm;
	
	my @pvs_ = grep { !$vg->{parts} || $vg->{parts} =~ /\Q$_->{mntpoint}/ } @pvs;
	@pvs = difference2(\@pvs, \@pvs_);

	foreach my $part (@pvs_) {
	    raid::make($all_hds->{raids}, $part) if isRAID($part);
	    $part->{lvm} = $lvm->{VG_name};
	    delete $part->{mntpoint};
	    lvm::vg_add($part);
	    push @{$lvm->{disks}}, $part;
	}
	lvm::update_size($lvm);
    }
    1;
}

sub change_type {
    my ($type, $hd, $part) = @_;
    $type->{pt_type} != $part->{pt_type} || $type->{fs_type} ne $part->{fs_type} or return;
    fs::type::check($type->{fs_type}, $hd, $part);
    delete $part->{device_UUID};
    $hd->{isDirty} = 1;
    $part->{mntpoint} = '' if isSwap($part) && $part->{mntpoint} eq "swap";
    $part->{mntpoint} = '' if fs::type::cannotBeMountable($part);
    set_isFormatted($part, 0);
    fs::type::set_type_subpart($part, $type);
    fs::mount_options::rationalize($part);
    1;
}

sub partition_table_clear_and_initialize {
    my ($lvms, $hd, $o_in, $o_type, $b_warn) = @_;
    $hd->clear_existing;
    partition_table_initialize($lvms, $hd, $o_in, $o_type, $b_warn);
}

sub partition_table_initialize {
    my ($lvms, $hd, $o_in, $o_type, $b_warn) = @_;
    partition_table::initialize($hd, $o_type);
    if ($hd->isa('partition_table::lvm')) {
	if ($b_warn && $o_in) {
	    $o_in->ask_okcancel_('', N("ALL existing partitions and their data will be lost on drive %s", partition_table::description($hd))) or return;
	}
	require lvm;
	lvm::check($o_in ? $o_in->do_pkgs : do_pkgs_standalone->new) if $::isStandalone;
	lvm::create_singleton_vg($lvms, fs::get::hds_fstab($hd));
    }
}

1;