diff options
Diffstat (limited to 'perl-install/fs')
| -rw-r--r-- | perl-install/fs/any.pm | 176 | ||||
| -rw-r--r-- | perl-install/fs/dmcrypt.pm | 216 | ||||
| -rw-r--r-- | perl-install/fs/dmraid.pm | 311 | ||||
| -rw-r--r-- | perl-install/fs/format.pm | 435 | ||||
| -rw-r--r-- | perl-install/fs/get.pm | 188 | ||||
| -rw-r--r-- | perl-install/fs/loopback.pm | 119 | ||||
| -rw-r--r-- | perl-install/fs/mount.pm | 235 | ||||
| -rw-r--r-- | perl-install/fs/mount_options.pm | 276 | ||||
| -rw-r--r-- | perl-install/fs/mount_point.pm | 136 | ||||
| -rw-r--r-- | perl-install/fs/partitioning.pm | 83 | ||||
| -rw-r--r-- | perl-install/fs/partitioning_wizard.pm | 655 | ||||
| -rw-r--r-- | perl-install/fs/proc_partitions.pm | 82 | ||||
| -rw-r--r-- | perl-install/fs/remote.pm | 45 | ||||
| -rw-r--r-- | perl-install/fs/remote/davfs.pm | 99 | ||||
| -rw-r--r-- | perl-install/fs/remote/nfs.pm | 74 | ||||
| -rw-r--r-- | perl-install/fs/remote/smb.pm | 218 | ||||
| -rw-r--r-- | perl-install/fs/type.pm | 456 | ||||
| -rw-r--r-- | perl-install/fs/wild_device.pm | 115 | 
18 files changed, 3919 insertions, 0 deletions
| diff --git a/perl-install/fs/any.pm b/perl-install/fs/any.pm new file mode 100644 index 000000000..ce4de2ca3 --- /dev/null +++ b/perl-install/fs/any.pm @@ -0,0 +1,176 @@ +package fs::any; + +use diagnostics; +use strict; + +use c; +use common; +use fsedit; +use fs::get; +use fs::mount_point; +use fs::type; +use run_program; + +sub get_hds { +    my ($all_hds, $fstab, $manual_fstab, $partitioning_flags, $skip_mtab, $o_in) = @_; + +    my $probed_all_hds = fsedit::get_hds($partitioning_flags, $o_in); +    my $hds = $probed_all_hds->{hds}; + +    if (is_empty_array_ref($hds)) { #- no way +	die N("An error occurred - no valid devices were found on which to create new filesystems. Please check your hardware for the cause of this problem"); +    } + +    #- try to figure out if the same number of hds is available, use them if ok. +    @{$all_hds->{hds} || []} == @$hds and return 1; + +    fs::get_raw_hds('', $probed_all_hds); +    fs::add2all_hds($probed_all_hds, @$manual_fstab); + +    %$all_hds = %$probed_all_hds; +    @$fstab = fs::get::really_all_fstab($all_hds); + +    if (!$skip_mtab) { +        #- do not mount the windows partition +        fs::merge_info_from_mtab($fstab); +        fs::mount_point::suggest_mount_points_always($fstab); +    } + +    1; +} + +sub write_hds { +    my ($all_hds, $fstab, $set_mount_defaults, $on_reboot_needed, $opts) = @_; +    if (!$::testing) { +	my $hds = $all_hds->{hds}; +	partition_table::write($_) foreach @$hds; +	$_->{rebootNeeded} and $on_reboot_needed->() foreach @$hds; +    } + +    fs::set_removable_mntpoints($all_hds); +    fs::mount_options::set_all_default($all_hds, %$opts, lang::fs_options($opts->{locale})) +	if $set_mount_defaults; + +    @$fstab = fs::get::fstab($all_hds); +} + +sub set_cdrom_symlink { +    my ($raw_hds) = @_; + +    foreach (grep { $_->{media_type} eq 'cdrom' } @$raw_hds) { +	next if $_->{device_alias}; +	my $alias = basename($_->{mntpoint}) or next; +	log::l("using alias $alias for $_->{device}"); +	$_->{device_alias} = $alias; +	symlink($_->{device}, "/dev/$alias"); +    } +} + +sub check_hds_boot_and_root { +    my ($all_hds, $fstab, $isUpgrade, $o_match_all_hardware) = @_; +    fs::get::root_($fstab) or die "Oops, no root partition"; + +    return if $o_match_all_hardware || $::local_install; + +    if (is_uefi()) { +	if (!fs::get::has_mntpoint("/boot/EFI", $all_hds)) { +	    die N("You must have a ESP FAT32 partition mounted in /boot/EFI"); +	} +    } else { +	# if we are doing an upgrade, the user may still be using a legacy bootloader +	return if $isUpgrade; +	if (is_boot_bios_part_needed($all_hds)) { +	    die N("You must have a BIOS boot partition for non-UEFI GPT-partitioned disks. Please create one before continuing."); +	} +    } +} + +sub create_minimal_files() { +    mkdir "$::prefix/$_", 0755 foreach  +      qw(dev etc etc/profile.d etc/rpm etc/sysconfig etc/sysconfig/console  +	etc/sysconfig/network-scripts etc/sysconfig/console/consolefonts  +	etc/sysconfig/console/consoletrans +	home mnt run tmp var var/tmp var/lib var/lib/rpm var/lib/urpmi); +    mkdir "$::prefix/$_", 0700 foreach qw(root root/tmp root/drakx); +} + +sub prepare_minimal_root() { + +    create_minimal_files(); + +    # ensure we've all needed devices, for bootloader install and mkinitrd: +    run_program::run('mount', '--bind', '/dev', "$::prefix/dev"); +    run_program::run('mount', '--bind', '/run', "$::prefix/run"); +    eval { fs::mount::mount('none', "$::prefix/proc", 'proc') }; +    eval { fs::mount::mount('none', "$::prefix/sys", 'sysfs') }; +    eval { fs::mount::sys_kernel_debug($::prefix) }; +} + +sub getNeededMinSpace { +    my ($n) = @_; + +    #- make sure of this place to be available for installation, this could help a lot. +    #- currently doing a very small install use 36Mb of postinstall-rpm, but installing +    #- these packages may eat up to 90Mb (of course not all the server may be installed!). +    #- 65mb may be a good choice to avoid almost all problem of insuficient space left... +    my $minAvailableSize = 65 * sqr(1024); + +    max(0.1 * $n, $minAvailableSize); +} + +sub getAvailableSpace { +    my ($fstab, $o_skip_mounted, $o_skip_min_space) = @_; + +    my $n = !$::testing && !$o_skip_mounted && getAvailableSpace_mounted($::prefix) ||  +            getAvailableSpace_raw($fstab) * 512 / 1.07; +    $o_skip_min_space ? $n : $n - getNeededMinSpace($n); +} + +sub getAvailableSpace_mounted { +    my ($prefix) = @_; +    my $dir = -d "$prefix/usr" ? "$prefix/usr" : $prefix; +    my (undef, $free) = MDK::Common::System::df($dir) or return; +    log::l("getAvailableSpace_mounted $free KB"); +    $free * 1024 || 1; +} +sub getAvailableSpace_raw { +    my ($fstab) = @_; + +    do { $_->{mntpoint} eq '/usr' and return $_->{size} } foreach @$fstab; +    do { $_->{mntpoint} eq '/'    and return $_->{size} } foreach @$fstab; + +    if ($::testing) { +	my $nb = 450; +	log::l("taking ${nb}MB for testing"); +	return MB($nb); +    } +    die "missing root partition"; +} + +=head3 is_boot_bios_part_needed($all_hds) + +Returns whether a Boot BIOS Partition is needed + +Returns true if all of the following are true: +  - legacy boot (not UEFI) +  - all disks are (or will be) GPT +  - no disks have a BIOS boot partition + +=cut + +sub is_boot_bios_part_needed { +    my ($all_hds) = @_; +    # never needed for UEFI boot +    return if is_uefi(); +    # do we already have one? +    my @parts = map { partition_table::get_normal_parts($_) } fs::get::hds($all_hds); +    return if any { isBIOS_GRUB($_) } @parts; +    # do we have any non-GPT disks? +    foreach my $hd (@{$all_hds->{hds}}) { +	my $type = $hd->{pt_table_type} || partition_table::default_type($hd); +	return if $type ne 'gpt'; +    } +    1; +} + +1; diff --git a/perl-install/fs/dmcrypt.pm b/perl-install/fs/dmcrypt.pm new file mode 100644 index 000000000..a78a495d1 --- /dev/null +++ b/perl-install/fs/dmcrypt.pm @@ -0,0 +1,216 @@ +package fs::dmcrypt; + +use diagnostics; +use strict; + +#-###################################################################################### +#- misc imports +#-###################################################################################### +use common; +use fs::type; +use fs::get; +use run_program; + +=head1 SYNOPSYS + +Manage encrypted file systems using cryptsetup + +=head1 Functions + +=over + +=cut + +sub _crypttab() { "$::prefix/etc/crypttab" } + +=item init() + +Load kernel modules and init device mapper. + +=cut + +sub init() { +    whereis_binary('cryptsetup') or die "cryptsetup not installed"; + +    eval { modules::load('dm-crypt', list_modules::category2modules('various/crypto')) }; +    devices::init_device_mapper(); +    1; +} +my $initialized; +sub _ensure_initialized() { +    $initialized++ or init(); +} + +sub read_crypttab_ { +    my ($all_hds, $crypttab) = @_; + +    -e $crypttab or return; + +    my @raw_parts = grep { fs::type::isRawLUKS($_) } fs::get::really_all_fstab($all_hds); + +    foreach (cat_($crypttab)) { +	next if /^#/; +	my ($dm_name, $dev) = split; + +	my $raw_part = fs::get::device2part($dev, \@raw_parts) +	  or log::l("crypttab: unknown device $dev for $dm_name"), next; + +	$raw_part->{dm_name} = $dm_name; +	_get_existing_one_with_state($raw_part); +    } +} + +=item read_crypttab($all_hds) + +Read /etc/crypttab + +=cut + +sub read_crypttab { +    my ($all_hds) = @_; +    read_crypttab_($all_hds, _crypttab()); +} + +sub save_crypttab_ { +    my ($all_hds, $crypttab) = @_; + +    my @raw_parts = grep { $_->{dm_name} } fs::get::really_all_fstab($all_hds) or return; + +    my %names = map { $_->{dm_name} => fs::wild_device::from_part('', $_) } @raw_parts; + +    substInFile { +	my ($name, $_dev) = split; +	if (my $new_dev = delete $names{$name}) { +	    $_ = "$name $new_dev\n"; +	} +	if (eof) { +	    $_ .= join('', map { "$_ $names{$_}\n" } sort keys %names); +	} +    } $crypttab; +} + +=item save_crypttab($all_hds) + +Save /etc/crypttab + +=cut + +sub save_crypttab { +    my ($all_hds) = @_; +    save_crypttab_($all_hds, _crypttab()); +} + +sub format_part { +    my ($part) = @_; + +    my $tmp_key_file = "/tmp/.dmcrypt_key-$$"; +    common::with_private_tmp_file($tmp_key_file, $part->{dmcrypt_key}, sub { +	_run_or_die('--cipher=aes-xts-benbi', '--key-size=512', 'luksFormat', '--batch-mode', devices::make($part->{device}), $_[0]); +    }); +    fs::format::after_formatting($part); +} + +sub open_part { +    my ($dmcrypts, $part) = @_; + +    my $tmp_key_file = "/tmp/.dmcrypt_key-$$"; +    common::with_private_tmp_file($tmp_key_file, $part->{dmcrypt_key}, sub { +	_run_or_die('luksOpen', devices::make($part->{device}),  +				$part->{dm_name}, '--key-file', $_[0]); +    }); +    run_program::run('udevadm', 'settle'); + +    push @$dmcrypts, _get_existing_one_with_state($part); +} + + +sub _get_existing_one_with_state { +    my ($part) = @_; +    my $active_dmcrypt = _parse_dmsetup_table($part->{dm_name},  +					      run_program::get_stdout('dmsetup', 'table', $part->{dm_name})); +    _get_existing_one([$part], $active_dmcrypt); +} + +sub close_part { +    my ($dmcrypts, $part) = @_; +    my $dm_part = fs::get::device2part("mapper/$part->{dm_name}", $dmcrypts); +    _run_or_die('luksClose', devices::make($dm_part->{device})); +    $part->{dm_active} = 0; +    @$dmcrypts = grep { $_ != $dm_part } @$dmcrypts; +    # remove partition from /etc/crypttab when deleted (mga#25891) +    substInFile { +	my ($name, $_dev) = split; +	undef $_ if $name eq $part->{dm_name}; +    } _crypttab(); +} + +sub _run_or_die { +    my ($command, @para) = @_; + +    _ensure_initialized(); + +    run_program::run_or_die('cryptsetup', $command, @para); +} + +sub get_existing { +    my $fstab = \@_; +    map { _get_existing_one($fstab, $_) } active_dmcrypts(); +} + +sub _get_existing_one { +    my ($fstab, $active_dmcrypt) = @_; + +    my $p = fs::wild_device::to_subpart("/dev/mapper/$active_dmcrypt->{name}"); + +    my $part = { device => "mapper/$active_dmcrypt->{name}", size => $active_dmcrypt->{size},  +		 options => 'noatime', dmcrypt_name => $active_dmcrypt->{name}, +		 major => $p->{major}, minor => $p->{minor} }; + +    if (my $raw_part = find { fs::get::is_same_hd($active_dmcrypt, $_) } @$fstab) { +	$part->{rootDevice} = $raw_part->{device}; +	$raw_part->{dm_name} = $active_dmcrypt->{name}; +	$raw_part->{dm_active} = 1; +    } else { +	log::l("could not find the device $active_dmcrypt->{major}:$active_dmcrypt->{minor} for $part->{device}"); +    } + +    if (my $type = fs::type::type_subpart_from_magic($part)) { +	put_in_hash($part, $type); +    } +    fs::type::set_isFormatted($part, to_bool($part->{fs_type})); + +    unless (fs::type::cannotBeMountable($part)) { +	$part->{fs_type} or fs::type::set_fs_type($part, defaultFS()); +    } + +    log::l("dmcrypt: found $part->{device} type $part->{fs_type} with rootDevice $part->{rootDevice}"); + +    $part; +} + +sub active_dmcrypts() { +    grep { $_->{type} eq 'crypt' } active_dm(); +} + +sub _parse_dmsetup_table { +    my ($name, $s) = @_; + +    my @l = split(' ', $s); +    my ($major, $minor) = split(':', $l[6]); +    { name => $name, size => $l[1], type => $l[2], major => $major, minor => $minor }; +} + +sub active_dm() { +    run_program::run('udevadm', 'settle'); + +    map { +	my $name = s/(.*?):\s*// && $1; +	_parse_dmsetup_table($name, $_); +    } run_program::get_stdout('dmsetup', 'table'); +} + +=back + +=cut + +1; diff --git a/perl-install/fs/dmraid.pm b/perl-install/fs/dmraid.pm new file mode 100644 index 000000000..7a279abc7 --- /dev/null +++ b/perl-install/fs/dmraid.pm @@ -0,0 +1,311 @@ +package fs::dmraid; + +use diagnostics; +use strict; + +#-###################################################################################### +#- misc imports +#-###################################################################################### +use common; +use modules; +use devices; +use fs::type; +use fs::wild_device; +use run_program; + + +=head1 SYNOPSYS + +Manage fake RAIDs using dmraid + +=head1 Functions + +=over + +=item init() + +Load kernel modules, init device mapper then scan for fake RAIDs. + +=cut + +sub init() { +    whereis_binary('dmraid') or die "dmraid not installed"; + +    eval { modules::load('dm-mirror', 'dm-zero') }; +    devices::init_device_mapper(); +    if ($::isInstall) { +        foreach my $name (call_dmraid('-s', '-c', '-i')) { +	    chomp($name); +	    log::l("got: $name"); +	    call_dmraid('-ay', '-i', '--rm_partitions', '-p', $name); +	    run_program::run('/sbin/kpartx', '-u', '-a', '/dev/mapper/' . $name); +        } +    } +    1; +} + +=item call_dmraid($option, @args) + +Runs dmraid with $option & @args. +It is overloaded when debugging, see the end of this file. + +=cut + +sub call_dmraid { +    my ($option, @args) = @_; +    run_program::get_stdout('dmraid', $option, @args); +} + +=item check($in) + +Ensures dmraid is installed. If yes, calls init(). + +=cut + +sub check { +    my ($in) = @_; + +    $in->do_pkgs->ensure_binary_is_installed('dmraid', 'dmraid') or return; +    init(); +    1; +} + +=item _raid_devices_raw() + +Get the real VG names, needed for ddf1, and safer than begins_with for raid10 + +=cut + +sub _raid_devices_raw() { +    log::l("_raid_devices_raw"); +    my %vgs; +    my %pv2vg = map { +	chomp(); +	log::l("got: $_"); +	my %l; @l{qw(name size stride level status subsets devs spares)} = split(':'); +	$vgs{$l{name}} = 1 if defined $l{spares}; +	if (/freeing device "(.*)", path "(.*)"/ && defined $vgs{$1}) { +	    log::l("$2 => $1"); +	    $2 => $1; +        } +    } call_dmraid(qw(-d -s -c -c)); + +    map { +	chomp; +	log::l("got: $_"); +	my %l; @l{qw(pv format vg level status size)} = split(':'); +	if (defined $l{size} && defined $l{vg} && defined $pv2vg{$l{pv}} && !defined $vgs{$l{vg}}) { +	    log::l("using $pv2vg{$l{pv}} instead of $l{vg}"); +	    $l{vg} = $pv2vg{$l{pv}}; +	} +	if_(defined $l{size}, \%l); +    } call_dmraid(qw(-r -c -c)); +} + +sub _raid_devices() { +    my @l = _raid_devices_raw(); +    my %vg2pv; push @{$vg2pv{$_->{vg}}}, delete $_->{pv} foreach @l; +    my %vg2status; push @{$vg2status{$_->{vg}}}, delete $_->{status} foreach @l; +    map { +	delete $_->{size}; #- now irrelevant +	$_->{disks} = $vg2pv{$_->{vg}}; +	$_->{status} = (every { $_ eq 'ok' } @{$vg2status{$_->{vg}}}) ? 'ok' : join(' ', @{$vg2status{$_->{vg}}}); +	$_; +    } uniq_ { $_->{vg} } @l; +} + +sub _sets_raw() { +    map { +	chomp; +	log::l("got: $_"); +	my %l; @l{qw(name size stride level status subsets devs spares)} = split(':'); +	if_(defined $l{spares}, \%l); +    } call_dmraid('-s', '-c', '-c'); +} + +sub _sets() { +    my @sets = _sets_raw(); +    my @raid_devices = _raid_devices(); +    foreach (@sets) { +	my $name = $_->{name}; +	my @l = grep { begins_with($name, $_->{vg}) } @raid_devices; +	log::l("ERROR: multiple match for set $name: " . join(' ', map { $_->{vg} } @l)) if @l > 1; + +	@l = grep { begins_with($_->{vg}, $name) } @raid_devices if !@l; +	 +	if (@l) { +	    foreach my $raid (@l) { +		push @{$_->{disks}}, @{$raid->{disks}}; +		add2hash($_, $raid); +		$_->{status} = $raid->{status} if $_->{status} eq 'ok' && $::isInstall; +	    } +	} else { +	    log::l("ERROR: no matching raid devices for set $name"); +	} +    } +    @sets; +} + +=item vgs() + +Returns the list of VGs corresponding to dmraid + +=cut + +sub vgs() { +    map { +	my $dev = "mapper/$_->{name}"; +	my $vg = fs::wild_device::to_subpart("/dev/$dev"); +	add2hash($vg, { media_type => 'hd', bus => "dmraid_$_->{format}", disks => $_->{disks} }); + +	#- device should exist, created by dmraid(8) using libdevmapper +	#- if it doesn't, we suppose it's not in use +	if (-e "/dev/$dev") { +	    $vg;  +	} else { +	    log::l("ignoring $dev as /dev/$dev doesn't exist"); +	    (); +	} + +    } grep {  +	if ($_->{status} eq 'ok') { +	    1; +	} else { +	    call_dmraid('-an', $_->{vg}) if $::isInstall; #- for things like bad_sil below, deactivating half activated dmraid +	    0; +	} +    } _sets(); +} + +=item migrate_device_names ($vg) + +Handles migration from /dev/mapper/xxx1 to /dev/mapper/xxxp1, as used by initrd/nash. +dmraid has been patched to follow xxxp1 device names. +So until the box has rebooted on new initrd/dmraid, we must cope with /dev/mapper/xxx1 device names +(cf mdk#44182) + +=cut + +sub migrate_device_names { +    my ($vg) = @_; + +    my $dev_name = basename($vg->{device}); +    foreach (all('/dev/mapper')) { +	my ($nb) = /^\Q$dev_name\E(\d+)$/ or next; +	my $new = $dev_name . 'p' . $nb; +	if (! -e "/dev/mapper/$new") { +	    log::l("migrating to $new, creating a compat symlink $_"); +	    rename "/dev/mapper/$_", "/dev/mapper/$new"; +	    symlink $new, "/dev/mapper/$_"; +	} +    } +} + +=back + +=head1 Debugging + +If $ENV{DRAKX_DEBUG_DMRAID} is set, debugging dmraid is done. +The C<call_dmraid()> function is overloaded and will spit out warnings. +=cut + +if ($ENV{DRAKX_DEBUG_DMRAID}) { +    eval(<<'EOF'); +    my %debug_data = ( +     +     isw => { +     +      # dmraid -s #################### +      # *** Group superset isw_ffafgbdhi +      # --> Active Subset +      # name   : isw_ffafgbdhi_toto +      # size   : 234441216 +      # stride : 256 +      # type   : mirror +      # status : ok +      # subsets: 0 +      # devs   : 2 +      # spares : 0 +     +      '-s' => "isw_ffafgbdhi_toto:234441216:256:mirror:ok:0:2:0\n", +     +      # dmraid -r #################### +      #/dev/sda: isw, "isw_ffafgbdhi", GROUP, ok, 488397166 sectors, data@ 0 +      #/dev/sdb: isw, "isw_ffafgbdhi", GROUP, ok, 234441646 sectors, data@ 0 +     +      '-r' => "/dev/sda:isw:isw_ffafgbdhi:GROUP:ok:488397166:0\n" . +              "/dev/sdb:isw:isw_ffafgbdhi:GROUP:ok:234441646:0\n", +     }, +     +     pdc => { +      # dmraid -s #################### +      # *** Active Set +      # name   : pdc_bcefbiigfg +      # size   : 80043200 +      # stride : 128 +      # type   : mirror +      # status : ok +      # subsets: 0 +      # devs   : 2 +      # spares : 0 +     +      '-s' => "pdc_bcefbiigfg:80043200:128:mirror:ok:0:2:0\n", +     +      # dmraid -r #################### +      # /dev/sda: pdc, "pdc_bcefbiigfg", mirror, ok, 80043200 sectors, data@ 0 +      # /dev/sdb: pdc, "pdc_bcefbiigfg", mirror, ok, 80043200 sectors, data@ 0 +     +      '-r' => "/dev/sda:pdc:pdc_bcefbiigfg:mirror:ok:80043200:0\n" . +                "/dev/sdb:pdc:pdc_bcefbiigfg:mirror:ok:80043200:0\n", +     }, +     +     bad_sil => { +      '-s' => "sil_aeacdidecbcb:234439600:0:mirror:ok:0:1:0\n", +                # ERROR: sil: only 3/4 metadata areas found on /dev/sdb, electing... + +      '-r' => "/dev/sdb:sil:sil_aeacdidecbcb:mirror:broken:234439600:0\n", +                # ERROR: sil: only 3/4 metadata areas found on /dev/sdb, electing... +     }, + +     weird_nvidia =>  { +      '-s' => <<'EO', +/dev/sda: "sil" and "nvidia" formats discovered (using nvidia)! +/dev/sdb: "sil" and "nvidia" formats discovered (using nvidia)! +nvidia_bcjdbjfa:586114702:128:mirror:ok:0:2:0 +EO +       '-r' => <<'EO', +/dev/sda: "sil" and "nvidia" formats discovered (using nvidia)! +/dev/sdb: "sil" and "nvidia" formats discovered (using nvidia)! +/dev/sda:nvidia:nvidia_bcjdbjfa:mirror:ok:586114702:0 +/dev/sdb:nvidia:nvidia_bcjdbjfa:mirror:ok:586114702:0 +EO +	 # ERROR: multiple match for set nvidia_bcjdbjfa:  nvidia_bcjdbjfa +     }, + +     nvidia_with_subsets => { +      '-s' => <<'EO', +nvidia_bfcciffh:625163520:128:raid10:ok:2:4:0 +EO +       '-r' => <<'EO', +/dev/sda:nvidia:nvidia_bfcciffh-0:stripe:ok:312581806:0 +/dev/sdb:nvidia:nvidia_bfcciffh-0:stripe:ok:312581806:0 +/dev/sdc:nvidia:nvidia_bfcciffh-1:stripe:ok:312581806:0 +/dev/sdd:nvidia:nvidia_bfcciffh-1:stripe:ok:312581806:0 +EO +	 # ERROR: multiple match for set nvidia_bcjdbjfa:  nvidia_bcjdbjfa +     }, +    ); +     +    *call_dmraid = sub { +        my ($option, @args) = @_; +        if (my $s = $debug_data{$ENV{DRAKX_DEBUG_DMRAID}}{$option}) { +            split("\n", $s); +	} else { +            warn "dmraid $option @args\n"; +        } +    }; +EOF +    $@ and die; +} + +1; diff --git a/perl-install/fs/format.pm b/perl-install/fs/format.pm new file mode 100644 index 000000000..1fa90c29e --- /dev/null +++ b/perl-install/fs/format.pm @@ -0,0 +1,435 @@ +package fs::format; + +use diagnostics; +use strict; +use String::ShellQuote; + +use run_program; +use common; +use fs::type; +use fs::loopback; +use log; + +=head1 SYNOPSYS + +B<fs::format> enables to format filesystems. + +=head1 Variables + +=over + +=item %cmds + +Commands to format filesystem: + +For each filesystem, list: [ package_name, command_to_use, options_to_use ] + +=cut + +my %cmds = ( +    ext2     => [ 'e2fsprogs', 'mkfs.ext2', '-F' ], +    ext3     => [ 'e2fsprogs', 'mkfs.ext3', '-F' ], +    ext4     => [ 'e2fsprogs', 'mkfs.ext4', '-F' ], +    f2fs     => [ 'f2fs-tools', 'mkfs.f2fs', '-f' ], +    reiserfs => [ 'reiserfsprogs', 'mkfs.reiserfs', '-ff' ], +    xfs      => [ 'xfsprogs', 'mkfs.xfs', '-f', '-q' ], +    jfs      => [ 'jfsutils', 'mkfs.jfs', '-f' ], +    hfs      => [ 'hfsutils', 'hformat' ], +    dos      => [ 'dosfstools', 'mkfs.fat' ], +    vfat     => [ 'dosfstools', 'mkfs.fat', '-F', '32' ], +    exfat    => [ 'exfatprogs', 'mkfs.exfat' ], +    swap     => [ 'util-linux', 'mkswap' ], +    ntfs     => [ 'ntfs-3g', 'mkfs.ntfs', '--fast' ], +   'ntfs-3g' => [ 'ntfs-3g', 'mkfs.ntfs', '--fast' ], +    btrfs    => [ 'btrfs-progs', 'mkfs.btrfs', '-f' ], +    nilfs2   => [ 'nilfs-utils', 'mkfs.nilfs2', '-f' ], +); + + +=item %LABELs + +mkfs option to use in order to set the label + label specs. + +For each filesystem, list: [ option, max_length, handled_by_mount ] + +=cut + +my %LABELs = ( +    ext2     => [ '-L', 16, 1 ], +    ext3     => [ '-L', 16, 1 ], +    ext4     => [ '-L', 16, 1 ], +    exfat    => [ '-L', 16, 1 ], +    f2fs     => [ '-l', 16, 1 ], +    reiserfs => [ '-l', 16, 1 ], +    xfs      => [ '-L', 12, 1 ], +    jfs      => [ '-L', 16, 1 ], +    hfs      => [ '-l', 27, 0 ], +    dos      => [ '-n', 11, 0 ], +    vfat     => [ '-n', 11, 0 ], +    swap     => [ '-L', 15, 1 ], +    ntfs     => [ '-L', 128, 0 ], +   'ntfs-3g' => [ '-L', 128, 0 ], +    btrfs    => [ '-L', 256, 1 ], +    nilfs2   => [ '-L', 16, 1 ], +); + +=item %edit_LABEL + +Commands to set the file system label. + +For each filesystem, list: [ package, command, option ] + +If option is defined, run <command> <option> <label> <device> + +If no option, run <command> <device> <label> + +=cut + +my %edit_LABEL = ( #  +    ext2     => [ 'e2fsprogs', 'tune2fs', '-L' ], +    ext3     => [ 'e2fsprogs', 'tune2fs', '-L' ], +    ext4     => [ 'e2fsprogs', 'tune2fs', '-L' ], +    reiserfs => [ 'reiserfsprogs', 'reiserfstune', '-l' ], +    xfs      => [ 'xfsprogs', 'xfs_admin', '-L' ], +    jfs      => [ 'jfsutils', 'jfs_tune', '-L' ], +#    hfs +    dos      => [ 'mtools', 'mlabel', '-i' ], +    exfat    => [ 'exfatprogs', 'tune.exfat', '-L' ], +    vfat     => [ 'mtools', 'mlabel', '-i' ], +    swap     => [ 'util-linux', 'swaplabel', '-L' ], +    ntfs     => [ 'ntfs-3g', 'ntfslabel' ], +   'ntfs-3g' => [ 'ntfs-3g', 'ntfslabel' ], +    btrfs    => [ 'btrfs-progs', 'btrfs', 'filesystem', 'label' ], +    nilfs2   => [ 'nilfs-utils', 'nilfs-tune', '-L' ], +); + +=item %preserve_UUID + +For each filesystem, list: [ option, max_length, handled_by_mount ] + +Those are used in order to preserve UUID on fs where we couldn't enforce it while formatting. + +=cut + +my %preserve_UUID = ( # package, command +    jfs      => [ 'jfsutils', 'jfs_tune', ], +    xfs      => [ 'xfsprogs', 'xfs_admin' ], +    nilfs2   => [ 'nilfs-utils', 'nilfs-tune' ], +); + + +=back + +=head1 Functions + +=over + +=item package_needed_for_partition_type($part) + +Return the package needed for that partition's type. + +=cut + +sub package_needed_for_partition_type { +    my ($part) = @_; +    my $l = $cmds{$part->{fs_type}} or return; +    $l->[0]; +} + +sub known_type { +    my ($part) = @_; +    to_bool($cmds{$part->{fs_type}}); +} + +sub check_package_is_installed_format { +    my ($do_pkgs, $fs_type) = @_; + +    my ($pkg, $binary) = @{$cmds{$fs_type} || return}; +    whereis_binary($binary) || $do_pkgs->ensure_binary_is_installed($pkg, $binary); #- ensure_binary_is_installed checks binary chrooted, whereas we run the binary non-chrooted (pb for Mageia One) +} + +sub check_package_is_installed_label { +    my ($do_pkgs, $fs_type) = @_; + +    my ($pkg, $binary) = @{$edit_LABEL{$fs_type} || return}; +    whereis_binary($binary) || $do_pkgs->ensure_binary_is_installed($pkg, $binary); #- ensure_binary_is_installed checks binary chrooted, whereas we run the binary non-chrooted (pb for Mageia One) +} + +sub canEditLabel { +    my ($part) = @_; +    to_bool($edit_LABEL{$part->{fs_type}}); +} + +=item part($all_hds, $part, $wait_message) + +Frontend to part_raw() + +=cut + +sub part { +    my ($all_hds, $part, $wait_message) = @_; +    if (isRAID($part)) { +	$wait_message->(N("Formatting partition %s", $part->{device})) if $wait_message; +	require raid; +	raid::format_part($all_hds->{raids}, $part); +    } elsif (isLoopback($part)) { +	$wait_message->(N("Creating and formatting file %s", $part->{loopback_file})) if $wait_message; +	fs::loopback::format_part($part); +    } else { +	$wait_message->(N("Formatting partition %s", $part->{device})) if $wait_message; +	part_raw($part, $wait_message); +    } +    undef $part->{toFormat}; +} + +=item write_label($part) + +Set the label on the filesystem hold in $part. + +=cut + +sub write_label { +    my ($part) = @_; + +    $part->{device_LABEL_changed} or return; +    maybeFormatted($part) or return; + +    if ($part->{encrypt_key}) { +	fs::mount::set_loop($part); +    } + +    my $dev = $part->{real_device} || $part->{device}; +    my ($_pkg, $cmd, @first_options) = @{$edit_LABEL{$part->{fs_type}} || die N("I do not know how to set label on %s with type %s", $part->{device}, $part->{fs_type})}; +    my @args; +    if ($cmd eq 'mlabel') { +      @args = ($cmd, @first_options, devices::make($dev), '::' . $part->{device_LABEL}); +    } elsif ($cmd eq 'btrfs') { +      # btrfs needs reverse ordering +      @args = ($cmd, @first_options, devices::make($dev), $part->{device_LABEL}); +    } elsif (defined $first_options[0]) { +      @args = ($cmd, @first_options, $part->{device_LABEL}, devices::make($dev)); +    } else { +      @args = ($cmd, devices::make($dev), $part->{device_LABEL}); +    } +    run_program::raw({ timeout => 'never' }, @args) or die N("setting label on %s failed, is it formatted?", $dev); +    delete $part->{device_LABEL_changed}; +} + +sub write_btrfs_uuid { +    my ($UUID, $dev) = @_; +    $dev = devices::make($dev); +    my $status = system("echo y|btrfstune -U $UUID $dev") == 0; +    die "failed to set UUID to '$UUID' on $dev (status=$status)" if !$status; +} + +=item sub option_to_preserve_UUID_while_formating($part, $fs_type) + +Return the options needed to preserve UUID while formating + +=cut + +sub option_to_preserve_UUID_while_formating { +    my ($part, $fs_type) = @_; +    if (member($fs_type, qw(swap ext2 ext3 ext4))) { +	return '-U', $part->{device_UUID} if $part->{device_UUID}; +    } elsif ($fs_type eq 'reiserfs') { +	return '-u', $part->{device_UUID} if $part->{device_UUID}; +    } +    return (); +} + +=item part_raw($part, $wait_message) + +Actually format the $part partition disk. $wait_message is only used when formating ext3/4. +If not set, ext[3-4] will be formated without any progression bar, like other fses... + +=cut + +sub part_raw { +    my ($part, $wait_message) = @_; + +    $part->{isFormatted} and return; + +    if ($part->{encrypt_key}) { +	fs::mount::set_loop($part); +    } + +    my $dev = $part->{real_device} || $part->{device}; + +    my @options = if_($part->{toFormatCheck}, "-c"); +    log::l("formatting device $dev (type $part->{fs_type})"); + +    my $fs_type = $part->{fs_type}; + +    if (member($fs_type, qw(ext2 ext3 ext4))) { +	push @options, "-m", "0" if $part->{mntpoint} =~ m|^/home|; +    } elsif (isDos($part)) { +	$fs_type = 'dos'; +    } elsif ($fs_type eq 'hfs') { +        push @options, '-l', "Untitled"; +    } elsif (isAppleBootstrap($part)) { +	push @options, '-l', 'bootstrap'; +    } + +    push @options, option_to_preserve_UUID_while_formating($part, $fs_type); +     +    if ($part->{device_LABEL}) { +	push @options, @{$LABELs{$fs_type}}[0], $part->{device_LABEL}; +    } + +    my ($_pkg, $cmd, @first_options) = @{$cmds{$fs_type} || die N("I do not know how to format %s in type %s", $part->{device}, $part->{fs_type})}; + +    my @args = ($cmd, @first_options, @options, devices::make($dev)); + +    if ($cmd =~ /^mkfs.ext[34]$/ && $wait_message) { +	mkfs_ext3($wait_message, @args) or die N("%s formatting of %s failed", $fs_type, $dev); +    } else { +	run_program::raw({ timeout => 'never' }, @args) or die N("%s formatting of %s failed", $fs_type, $dev); +    } + +    delete $part->{device_LABEL_changed}; + +    preserve_UUID_after_format($dev, $part, $fs_type); + +    if (member($fs_type, qw(ext3 ext4))) { +	disable_forced_fsck($dev); +    } + +    after_formatting($part); +} + +=item preserve_UUID_after_format($dev, $part, $fs_type) + +Preserve UUID on fs where we couldn't enforce it while formatting + +=cut + +sub preserve_UUID_after_format { +    my ($dev, $part, $fs_type) = @_; +    if (my $uuid_cmd = $preserve_UUID{$fs_type}) { +	my (undef, $cmd) = @$uuid_cmd; +	run_program::raw({}, $cmd, '-U', $part->{device_UUID}, devices::make($dev)) if $cmd; +    } elsif ($fs_type eq 'btrfs' && $part->{device_UUID}) { +	write_btrfs_uuid($part->{device_UUID}, $dev); +    } +} + +=item after_formatting($part) + +Misc post formating tags (rereading UUID & setting state) + +=cut + +sub after_formatting { +    my ($part) = @_; + +    my $p = fs::type::type_subpart_from_magic($part); +    $part->{device_UUID} = $p && $p->{device_UUID}; + +    set_isFormatted($part, 1); +} + +=item mkfs_ext3($wait_message, @args) + +Display a progression bar whike formating ext3/4 + +=cut + +sub mkfs_ext3 { +    my ($wait_message, @args) = @_; + +    my $cmd = shell_quote_best_effort(@args); +    log::l("running: $cmd"); +    open(my $F, "$cmd |"); + +    local $/ = "\b"; +    local $_; +    while (<$F>) { +	#- even if it still takes some time when format is over, we don't want the progress bar to stay at 85% +	$wait_message->('', $1, $2) if m!^\s*(\d+)/(\d+)\b!; +    } +    return close($F); +} + +=item disable_forced_fsck($dev) + +Disable automatic fsck on extX (infinite number of mounts & duration between 2 fsck runs) + +=cut + +sub disable_forced_fsck { +    my ($dev) = @_; +    run_program::run("tune2fs", "-c0", "-i0", devices::make($dev)); +} + +sub clean_label { +    my ($part) = @_; +    if ($part->{device_LABEL}) { +	my $fs_type = $part->{fs_type}; +	if ($LABELs{$fs_type}) { +	    my ($_option, $length, $handled_by_mount) = @{$LABELs{$fs_type}}; +	    if (length $part->{device_LABEL} > $length) { +		my $short = substr($part->{device_LABEL}, 0, $length); +		log::l("shortening LABEL $part->{device_LABEL} to $short"); +		$part->{device_LABEL} = $short; +	    } +	    delete $part->{prefer_device_LABEL} +	      if !$handled_by_mount || $part->{mntpoint} eq '/' && !member($fs_type, qw(ext2 ext3 ext4)); +	} else { +	    log::l("dropping LABEL=$part->{device_LABEL} since we don't know how to set labels for fs_type $fs_type"); +	    delete $part->{device_LABEL}; +	    delete $part->{prefer_device_LABEL}; +	    delete $part->{device_LABEL_changed}; +	} +    } +} + +sub formatMount_part { +    my ($part, $all_hds, $fstab, $wait_message) = @_; + +    if (isLoopback($part)) { +	formatMount_part($part->{loopback_device}, $all_hds, $fstab, $wait_message); +    } +    if (my $p = fs::get::up_mount_point($part->{mntpoint}, $fstab)) { +	formatMount_part($p, $all_hds, $fstab, $wait_message) if !fs::type::carry_root_loopback($part); +    } + +    clean_label($part); + +    if ($part->{toFormat}) { +	fs::format::part($all_hds, $part, $wait_message); +    } else { +	fs::format::write_label($part); +    } + +    #- setting user_xattr on /home (or "/" if no /home) +    if (!$part->{isMounted} && member($part->{fs_type}, qw(ext2 ext3 ext4)) +	  && ($part->{mntpoint} eq '/home' || +		!fs::get::has_mntpoint('/home', $all_hds) && $part->{mntpoint} eq '/')) { +	run_program::run('tune2fs', '-o', 'user_xattr', devices::make($part->{real_device} || $part->{device})); +    } + +    fs::mount::part($part, 0, $wait_message); +} + +sub formatMount_all { +    my ($all_hds, $fstab, $wait_message) = @_; +    formatMount_part($_, $all_hds, $fstab, $wait_message) +      foreach sort { isLoopback($a) ? 1 : isSwap($a) ? -1 : 0 } grep { $_->{mntpoint} } @$fstab; + +    #- ensure the link is there +    fs::loopback::carryRootCreateSymlink($_) foreach @$fstab; + +    #- for fun :) +    #- that way, when install exits via ctrl-c, it gives hand to partition +    eval { +	my ($_type, $major, $minor) = devices::entry(fs::get::root($fstab)->{device}); +	output "/proc/sys/kernel/real-root-dev", makedev($major, $minor); +    }; +} + +=back + +=cut + +1; diff --git a/perl-install/fs/get.pm b/perl-install/fs/get.pm new file mode 100644 index 000000000..00c807738 --- /dev/null +++ b/perl-install/fs/get.pm @@ -0,0 +1,188 @@ +package fs::get; + +use diagnostics; +use strict; + +use partition_table; +use fs::type; +use fs::loopback; +use fs::wild_device; +use fs; +use common; +use log; + + +=head1 SYNOPSYS + +B<fs::get>  + +=head1 Functions + +=over + +=cut + +sub empty_all_hds() { +    { hds => [], lvms => [], raids => [], dmcrypts => [], loopbacks => [], raw_hds => [], nfss => [], smbs => [], davs => [], special => [] }; +} +sub fstab { +    my ($all_hds) = @_; +    my @parts = map { partition_table::get_normal_parts($_) } hds($all_hds); +    @parts, @{$all_hds->{raids}}, @{$all_hds->{dmcrypts}}, @{$all_hds->{loopbacks}}; +} +sub really_all_fstab { +    my ($all_hds) = @_; +    my @l = fstab($all_hds); +    @l, (grep { !$_->{is_removable} } @{$all_hds->{raw_hds}}), @{$all_hds->{nfss}}, @{$all_hds->{smbs}}, @{$all_hds->{davs}}; +} + +sub fstab_and_holes { +    my ($all_hds, $b_non_readonly) = @_; +    my @hds = grep { !($b_non_readonly && $_->{readonly}) } hds($all_hds); +    hds_fstab_and_holes(@hds), @{$all_hds->{raids}}, @{$all_hds->{dmcrypts}}, @{$all_hds->{loopbacks}}; +} + +sub holes { +    my ($all_hds, $b_non_readonly) = @_; +    grep { isEmpty($_) } fstab_and_holes($all_hds, $b_non_readonly); +} +sub hds_holes { +    grep { isEmpty($_) } hds_fstab_and_holes(@_); +} +sub free_space { +    my ($all_hds) = @_; +    sum map { $_->{size} } holes($all_hds); +} +sub hds_free_space { +    sum map { $_->{size} } hds_holes(@_); +} + +sub hds { +    my ($all_hds) = @_; +    (@{$all_hds->{hds}}, @{$all_hds->{lvms}}); +} + +=item hds_fstab(@hds) + +Get all normal partition. + +=cut + +sub hds_fstab { +    map { partition_table::get_normal_parts($_) } @_; +} + +sub vg_free_space { +    my ($hd) = @_; +    my @parts = partition_table::get_normal_parts($hd); +    $hd->{totalsectors} - sum map { $_->{size} } @parts; +} + +sub hds_fstab_and_holes { +    map { +	if (isLVM($_)) { +	    my @parts = partition_table::get_normal_parts($_); +	    my $free = vg_free_space($_); +	    my $free_part = { start => 0, size => $free, pt_type => 0, rootDevice => $_->{VG_name} }; +	    @parts, if_($free >= $_->cylinder_size, $free_part); +	} else { +	    partition_table::get_normal_parts_and_holes($_); +	} +    } @_; +} + + +sub device2part { +    my ($dev, $fstab) = @_; +    my $subpart = fs::wild_device::to_subpart($dev); +    my $part = find { is_same_hd($subpart, $_) } @$fstab; +    log::l("fs::get::device2part: unknown device <<$dev>>") if !$part; +    $part; +} + +sub part2hd { +    my ($part, $all_hds) = @_; +    my $hd = find { $part->{rootDevice} eq ($_->{device} || $_->{VG_name}) } hds($all_hds); +    $hd; +} + +sub file2part { +    my ($fstab, $file, $b_keep_simple_symlinks) = @_;     +    my $part; + +    $file = $b_keep_simple_symlinks ? common::expand_symlinks_but_simple("$::prefix$file") : expand_symlinks("$::prefix$file"); +    unless ($file =~ s/^$::prefix//) { +	my $part = find { fs::type::carry_root_loopback($_) } @$fstab or die; +	log::l("found $part->{mntpoint}"); +	$file =~ s|/initrd/loopfs|$part->{mntpoint}|; +    } +    foreach (@$fstab) { +	my $m = $_->{mntpoint}; +	$part = $_ if  +	  $file =~ /^\Q$m/ &&  +	    (!$part || length $part->{mntpoint} < length $m); +    } +    $part or die "file2part: not found $file"; +    $file =~ s|$part->{mntpoint}/?|/|; +    ($part, $file); +} + +sub mntpoint2part { +    my ($mntpoint, $fstab) = @_; +    find { $mntpoint eq $_->{mntpoint} } @$fstab; +} +sub has_mntpoint { +    my ($mntpoint, $all_hds) = @_; +    mntpoint2part($mntpoint, [ really_all_fstab($all_hds) ]); +} + +sub root_from_mounted() { +    foreach (`df -P`) { +        next if m!^[^/]!; # ignore tootfs +        my ($fs, undef, undef, undef, undef, $mntpnt) = split(/\s+/); +        return $fs if $mntpnt eq '/'; +    } +} + +sub root_ { +    my ($fstab, $o_boot) = @_; +    $o_boot && mntpoint2part("/boot", $fstab) || mntpoint2part("/", $fstab); +} +sub root { &root_ || {} } + +sub up_mount_point { +    my ($mntpoint, $fstab) = @_; +    while (1) { +	$mntpoint = dirname($mntpoint); +	$mntpoint ne "." or return; +	$_->{mntpoint} eq $mntpoint and return $_ foreach @$fstab; +    } +} + +sub is_same_hd { +    my ($hd1, $hd2) = @_; +    if ($hd1->{major} && $hd2->{major}) { +	$hd1->{major} == $hd2->{major} && $hd1->{minor} == $hd2->{minor}; +    } elsif (my ($s1) = $hd1->{device} =~ m|https?://(.+?)/*$|) { +	my ($s2) = $hd2->{device} =~ m|https?://(.+?)/*$|; +	$s1 eq $s2; +    } else { +	$hd1->{device_LABEL} && $hd2->{device_LABEL} && $hd1->{device_LABEL} eq $hd2->{device_LABEL} +	  || $hd1->{device_UUID} && $hd2->{device_UUID} && $hd1->{device_UUID} eq $hd2->{device_UUID} +	  || $hd1->{device} && $hd2->{device} && $hd1->{device} eq $hd2->{device} +	  || $hd1->{device} && $hd2->{device_alias} && $hd1->{device} eq $hd2->{device_alias} +	  || $hd1->{device_alias} && $hd2->{device} && $hd1->{device_alias} eq $hd2->{device} +	  || $hd1->{device_alias} && $hd2->{device_alias} && $hd1->{device_alias} eq $hd2->{device_alias}; +    } +} + +sub mntpoint_prefixed { +    my ($part) = @_; +    $::prefix . $part->{mntpoint}; +} + +=back + +=cut + +1; diff --git a/perl-install/fs/loopback.pm b/perl-install/fs/loopback.pm new file mode 100644 index 000000000..8d0c729ec --- /dev/null +++ b/perl-install/fs/loopback.pm @@ -0,0 +1,119 @@ +package fs::loopback; + +use diagnostics; +use strict; + +#-###################################################################################### +#- misc imports +#-###################################################################################### +use common; +use fs::type; +use fs; +use log; + + +sub check_circular_mounts { +    my ($part, $all_hds) = @_; + +    my $fstab = [ fs::get::fstab($all_hds), $part ]; # no pb if $part is already in $all_hds + +    my $base_mntpoint = $part->{mntpoint}; +    my $check; $check = sub { +	my ($part, @seen) = @_; +	push @seen, $part->{mntpoint} || return; +	@seen > 1 && $part->{mntpoint} eq $base_mntpoint and die N("Circular mounts %s\n", join(", ", @seen)); +	if (my $part = fs::get::up_mount_point($part->{mntpoint}, $fstab)) { +	    #- '/' carrier is a special case, it will be mounted first +	    $check->($part, @seen) if !fs::type::carry_root_loopback($part); +	} +	if (isLoopback($part)) { +	    $check->($part->{loopback_device}, @seen); +	} +    }; +    $check->($part) if !($base_mntpoint eq '/' && isLoopback($part)); #- '/' is a special case, no loop check +} + +sub carryRootCreateSymlink { +    my ($part) = @_; + +    fs::type::carry_root_loopback($part) or return; + +    my $mntpoint = fs::get::mntpoint_prefixed($part); +    unless (-e $mntpoint) { +	eval { mkdir_p(dirname($mntpoint)) }; +	#- do non-relative link for install, should be changed to relative link before rebooting +	symlink "/initrd/loopfs", $mntpoint; + +	mkdir_p("/initrd/loopfs/lnx4win/boot"); +	symlink "/initrd/loopfs/lnx4win/boot", "$::prefix/boot"; +    } +    #- indicate kernel to keep initrd +    mkdir_p("$::prefix/initrd"); +} + + +sub format_part { +    my ($part) = @_; +    fs::mount::part($part->{loopback_device}); +    create($part); +    fs::format::part_raw($part, undef); +} + +sub create { +    my ($part) = @_; +    my $f = $part->{device} = fs::get::mntpoint_prefixed($part->{loopback_device}) . $part->{loopback_file}; +    return if -e $f; + +    eval { mkdir_p(dirname($f)) }; + +    log::l("creating loopback file $f ($part->{size} sectors)"); + +    my $block_size = 128; +    my $s = "\0" x (512 * $block_size); +    sysopen(my $F, $f, 2 | c::O_CREAT()) or die "failed to create loopback file"; +    for (my $i = 0; $i < $part->{size}; $i += $block_size) { +	syswrite $F, $s or die "failed to create loopback file"; +    } +} + +sub getFree { +    my ($dir, $part) = @_; +    my $freespace = $dir ?  +      2 * (MDK::Common::System::df($dir))[1] : #- df in KiB +      $part->{size}; + +    $freespace - sum map { $_->{size} } @{$part->{loopback} || []}; +} + +#- returns the size of the loopback file if it already exists +#- returns -1 is the loopback file cannot be used +sub verifFile { +    my ($dir, $file, $part) = @_; +    -e "$dir$file" and return -s "$dir$file"; + +    $_->{loopback_file} eq $file and return -1 foreach @{$part->{loopback} || []}; + +    undef; +} + +sub prepare_boot() { +    my $r = readlink "$::prefix/boot";  +    unlink "$::prefix/boot";  +    mkdir_p("$::prefix/boot"); +    [$r, $::prefix]; +} + +sub save_boot { +    my ($loop_boot, $prefix) = @{$_[0]}; +     +    $loop_boot or return; + +    my @files = glob_("$prefix/boot/*"); +    cp_af(@files, $loop_boot) if @files; +    rm_rf("$prefix/boot"); +    symlink $loop_boot, "$prefix/boot"; +} + + +1; + diff --git a/perl-install/fs/mount.pm b/perl-install/fs/mount.pm new file mode 100644 index 000000000..6afaa9260 --- /dev/null +++ b/perl-install/fs/mount.pm @@ -0,0 +1,235 @@ +package fs::mount; + +use diagnostics; +use strict; + +use run_program; +use common; +use fs::type; +use log; + + +sub set_loop { +    my ($part) = @_; +    $part->{device} ||= fs::get::mntpoint_prefixed($part->{loopback_device}) . $part->{loopback_file}; +    $part->{real_device} ||= devices::set_loop(devices::make($part->{device}), $part->{encrypt_key}, $part->{options} =~ /encryption=(\w+)/); +} + +sub swapon { +    my ($dev) = @_; +    log::l("swapon called with $dev"); +    syscall_('swapon', devices::make($dev), 0) or die "swapon($dev) failed: $!"; +} + +sub swapoff { +    my ($dev) = @_; +    syscall_('swapoff', devices::make($dev)) or die "swapoff($dev) failed: $!"; +} + +sub mount { +    my ($dev, $where, $fs, $b_rdonly, $o_options, $o_wait_message) = @_; +    log::l("mounting $dev on $where as type $fs, options $o_options"); + +    mkdir_p($where); + +    $fs or log::l("not mounting $dev partition"), return; + +    { +	my @fs_modules = qw(btrfs ext3 ext4 f2fs hfs jfs nilfs2 nfs ntfs romfs reiserfs ufs xfs vfat); +	my @types = (qw(ext2 proc sysfs iso9660 devpts auto ntfs-3g), @fs_modules); + +	push @types, 'smb', 'cifs', 'davfs2' if !$::isInstall; + +	if (!member($fs, @types)) { +	    log::l("skipping mounting $dev partition ($fs)"); +	    return; +	} +	if ($::isInstall) { +	    if (member($fs, @fs_modules)) { +		eval { modules::load($fs) }; +	    } elsif ($fs eq 'iso9660') { +		eval { modules::load('isofs') }; +	    } +	} +    } + +    $where =~ s|/$||; + +    my @mount_opt = split(',', $o_options || ''); + +    if ($::isInstall) { +	#- those options need nls_XXX modules, and we don't this at install +	@mount_opt = grep { $_ ne 'utf8' && !/^iocharset=/ } @mount_opt; +    } + +    if ($fs eq 'vfat') { +	@mount_opt = 'check=relaxed'; +    } elsif ($fs eq 'ntfs') { +	@mount_opt = () if $::isInstall; # esp. drop nls=xxx option so that we don't need kernel module nls_xxx +    } elsif ($fs eq 'nfs') { +	push @mount_opt, 'nolock', 'soft', 'intr' if $::isInstall; +    } elsif ($fs eq 'jfs' && !$b_rdonly) { +	fsck_jfs($dev, $o_wait_message); +    } elsif ($fs eq 'ext2' && !$b_rdonly) { +	fsck_ext2($dev, $o_wait_message); +    } elsif ($fs eq 'davfs2') { +	require fs::remote::davfs; +	# We have to store credentials in davfs2 secret file before mounting +	fs::remote::davfs::mountpoint_credentials_save($where, \@mount_opt); +	# username and password options should be handled by /etc/davfs2/secrets file +	@mount_opt = grep { !/^(username|password)=/ } @mount_opt; +    } + +    push @mount_opt, 'ro' if $b_rdonly; + +    $o_wait_message->(N("Mounting partition %s", $dev)) if $o_wait_message; +    modules::load("fuse") if $::isInstall && $fs eq 'ntfs-3g' && ! -e '/dev/fuse'; +    run_program::run('mount', '-t', $fs, $dev, $where, if_(@mount_opt, '-o', join(',', @mount_opt))) or die N("mounting partition %s in directory %s failed", $dev, $where); +} + +sub fsck_ext2 { +    my ($dev, $o_wait_message) = @_; +    $o_wait_message->(N("Checking %s", $dev)) if $o_wait_message; +    foreach ('-a', '-y') { +	run_program::raw({ timeout => 60 * 60 }, "fsck.ext2", $_, $dev); +	my $err = $?; +	if ($err & 0x0100) { +	    log::l("fsck corrected partition $dev"); +	} +	if ($err & 0xfeff) { +	    my $txt = sprintf("fsck failed on %s with exit code %d or signal %d", $dev, $err >> 8, $err & 255); +	    $_ eq '-y' ? die($txt) : cdie($txt); +	} else { +	    last; +	} +    } +} +sub fsck_jfs { +    my ($dev, $o_wait_message) = @_; +    $o_wait_message->(N("Checking %s", $dev)) if $o_wait_message; +    #- needed if the system is dirty otherwise mounting read-write simply fails +    run_program::raw({ timeout => 60 * 60 }, "fsck.jfs", $dev) or do { +	my $err = $?; +	die "fsck.jfs failed" if $err & 0xfc00; +    }; +} + +#- takes the mount point to umount (can also be the device) +sub umount { +    my ($mntpoint) = @_; +    $mntpoint =~ s|/$||; +    log::l("calling umount($mntpoint)"); + +    run_program::run('umount', $mntpoint) or do { +	kill 15, fuzzy_pidofs('^fam\b'); +	my $err; +	run_program::run('umount', '2>', \$err, $mntpoint) or die N("error unmounting %s: %s", $mntpoint, common::to_utf8($err)); +    }; + +    substInFile { $_ = '' if /(^|\s)$mntpoint\s/ } '/etc/mtab'; #- do not care about error, if we cannot read, we will not manage to write... (and mess mtab) +} + +sub part { +    my ($part, $b_rdonly, $o_wait_message) = @_; + +    log::l("mount_part: " . join(' ', map { "$_=$part->{$_}" } 'device', 'mntpoint', 'isMounted', 'real_mntpoint', 'device_UUID')); + +    return if $part->{isMounted} && !($part->{real_mntpoint} && $part->{mntpoint}); + +    unless ($::testing) { +	if (isSwap($part)) { +	    $o_wait_message->(N("Enabling swap partition %s", $part->{device})) if $o_wait_message; +	    swapon($part->{device}); +	} elsif ($part->{real_mntpoint}) { +	    my $mntpoint = fs::get::mntpoint_prefixed($part); + +	    mkdir_p($mntpoint); +	    run_program::run_or_die('mount', '--move', $part->{real_mntpoint}, $mntpoint); + +	    rmdir $part->{real_mntpoint}; +	    symlinkf $mntpoint, $part->{real_mntpoint}; +	    delete $part->{real_mntpoint}; + +	    my $dev = $part->{real_device} || fs::wild_device::from_part('', $part); +	    run_program::run_or_die('mount', $dev, $mntpoint, '-o', join(',', 'remount', $b_rdonly ? 'ro' : 'rw')); +	} else { +	    $part->{mntpoint} or die "missing mount point for partition $part->{device}"; + +	    my $mntpoint = fs::get::mntpoint_prefixed($part); +	    my $options = $part->{options}; +	    if ($part->{encrypt_key}) { +		set_loop($part); +		$options = join(',', grep { !/^(encryption=|encrypted$|loop$)/ } split(',', $options)); #- we take care of this, don't let it mount see it +	    } elsif (isLoopback($part)) { +		$options = join(',', uniq('loop', split(',', $options))); #- ensure the loop options is used +	    } elsif ($part->{options} =~ /encrypted/) { +		log::l("skip mounting $part->{device} since we do not have the encrypt_key"); +		return; +	    } elsif (fs::type::carry_root_loopback($part)) { +		$mntpoint = "/initrd/loopfs"; +	    } +	    my $dev = $part->{real_device} || fs::wild_device::from_part('', $part); +	    my $fs_type = $part->{fs_type}; +	    if ($fs_type eq 'auto' && $part->{media_type} eq 'cdrom' && $::isInstall) { +		$fs_type = 'iso9660'; +	    } elsif ($fs_type eq 'ntfs-3g' && $::isInstall) { +		$fs_type = 'ntfs'; +	    } +	    mount($dev, $mntpoint, $fs_type, $b_rdonly, $options, $o_wait_message); + +	    if ($options =~ /usrquota|grpquota/ && member($part->{fs_type}, qw(ext3 ext4))) { +		if (! find { -e "$mntpoint/$_" } qw(aquota.user aquota.group quota.user quota.group)) { +		    #- quotacheck will create aquota.user and/or aquota.group, +		    #- needed for quotas on ext3/ext4. +		    run_program::run('quotacheck', $mntpoint); +		}		 +	    } +	    if (isLoopback($part) && $::isInstall) { +		#- since /etc/mtab is symlinked to /proc/mounts, umount will +		#- not be able to know it needs to do losetup -d +		#- TODO: drop this and have a real /etc/mtab +		$part->{real_device} = cat_("/proc/mounts") =~ m!(/dev/\S+)\s+\Q$mntpoint\E\s! && $1; +		log::l("XXX $part->{real_device}"); +	    } +	} +    } +    $part->{isMounted} = 1; +    set_isFormatted($part, 1); #- assume that if mount works, partition is formatted +} + +sub umount_part { +    my ($part) = @_; + +    $part->{isMounted} or return; + +    unless ($::testing) { +	if (isSwap($part)) { +	    swapoff($part->{device}); +	} elsif (fs::type::carry_root_loopback($part)) { +	    umount("/initrd/loopfs"); +	} else { +	    umount($part->{real_mntpoint} || fs::get::mntpoint_prefixed($part) || devices::make($part->{device})); +	    devices::del_loop(delete $part->{real_device}) if $part->{real_device}; +	} +    } +    $part->{isMounted} = 0; +} + +sub umount_all { +    my ($fstab) = @_; + +    log::l("unmounting all filesystems"); + +    foreach (sort { $b->{mntpoint} cmp $a->{mntpoint} }  +	       grep { $_->{mntpoint} && !$_->{real_mntpoint} || isSwap($_) } @$fstab) { +	umount_part($_); +    } +} + +sub sys_kernel_debug { +    my ($prefix) = @_; +     +    mount('none', "$prefix/sys/kernel/debug/usb", 'debugfs'); +} + +1; diff --git a/perl-install/fs/mount_options.pm b/perl-install/fs/mount_options.pm new file mode 100644 index 000000000..0b63f7260 --- /dev/null +++ b/perl-install/fs/mount_options.pm @@ -0,0 +1,276 @@ +package fs::mount_options; + +use diagnostics; +use strict; + +use common; +use fs::type; +use fs::get; +use log; + +sub list() { +    my %non_defaults = ( +			sync => 'async', noatime => 'atime', noauto => 'auto', ro => 'rw',  +			user => 'nouser', nodev => 'dev', noexec => 'exec', nosuid => 'suid', +			user_xattr => 'nouser_xattr', +		       ); +    my @user_implies = qw(noexec nodev nosuid); +    \%non_defaults, \@user_implies; +} + +sub unpack { +    my ($part) = @_; +    my $packed_options = $part->{options}; + +    my ($non_defaults, $user_implies) = list(); + +    my @auto_fs = fs::type::guessed_by_mount(); +    my %per_fs = ( +		  iso9660 => [ qw(unhide) ], +		  vfat => [ qw(flush umask=0 umask=0022) ], +		  ntfs => [ qw(umask=0 umask=0022) ], +		  nfs => [ qw(rsize=8192 wsize=8192) ], +		  cifs => [ qw(username= password=) ], +		  davfs2 => [ qw(username= password= uid= gid=) ], +		  reiserfs => [ 'notail' ], +		 ); +    push @{$per_fs{$_}}, 'usrquota', 'grpquota' foreach 'ext2', 'ext3', 'ext4', 'xfs'; +    push @{$per_fs{$_}}, 'acl' foreach 'ext2', 'ext3', 'ext4', 'reiserfs'; + +    while (my ($fs, $l) = each %per_fs) { +	member($part->{fs_type}, $fs, 'auto') && member($fs, @auto_fs) or next; +	$non_defaults->{$_} = 1 foreach @$l; +    } + +    $non_defaults->{relatime} = 1 if isTrueLocalFS($part) || $part->{fs_type} eq 'ntfs-3g'; + +    my $defaults = { reverse %$non_defaults }; +    my %options = map { $_ => '' } keys %$non_defaults; +    my @unknown; +    foreach (split(",", $packed_options)) { +	if ($_ eq 'defaults') { +	    #- skip +	} elsif (member($_, 'user', 'users')) { +	    $options{$_} = 1 foreach $_, @$user_implies; +	} elsif (exists $non_defaults->{$_}) { +	    $options{$_} = 1; +	} elsif ($defaults->{$_}) { +	    $options{$defaults->{$_}} = 0; +	} elsif (/(.*?=)(.*)/) { +	    $options{$1} = $2; +	} else { +	    push @unknown, $_; +	} +    } +    # merge those, for cleaner help +    $options{'rsize=8192,wsize=8192'} = delete $options{'rsize=8192'} && delete $options{'wsize=8192'} +      if exists $options{'rsize=8192'}; + +    my $unknown = join(",", @unknown); +    \%options, $unknown; +} + +sub pack_ { +    my ($_part, $options, $unknown) = @_; + +    my ($non_defaults, $user_implies) = list(); +    my @l; + +    my @umasks = map { +	if (/^umask=/) { +	    my $v = delete $options->{$_}; +	    /^umask=(.+)/ ? if_($v, $1) : $v; +	} else { () } +    } keys %$options; +    if (@umasks) { +	push @l, 'umask=' . min(@umasks); +    } + +    if (my $user = find { delete $options->{$_} } 'users', 'user') { +	push @l, $user; +	delete $options->{user}; +	foreach (@$user_implies) { +	    if (!delete $options->{$_}) { +		# overriding +		$options->{$non_defaults->{$_}} = 1; +	    } +	} +    } +    push @l, map_each { if_($::b, $::a =~ /=$/ ? "$::a$::b" : $::a) } %$options; +    push @l, $unknown; + +    join(",", uniq(grep { $_ } @l)); +} +sub pack { +    my ($part, $options, $unknown) = @_; +    $unknown =~ s/ /,/g; +    $part->{options} = pack_($part, $options, $unknown) || 'defaults'; +    noreturn(); +} + +# update me on each util-linux new release: +sub help() { +    ( +	'acl' => N("Enable POSIX Access Control Lists"), + +	'flush' => N("Flush write cache on file close"), + +	'grpquota' => N("Enable group disk quota accounting and optionally enforce limits"), + +	'noatime' => N("Do not update inode access times on this filesystem +(e.g, for faster access on the news spool to speed up news servers)."), + +	'relatime' => N("Update inode access times on this filesystem in a more efficient way +(e.g, for faster access on the news spool to speed up news servers)."), + +	'noauto' => N("Can only be mounted explicitly (i.e., +the -a option will not cause the filesystem to be mounted)."), + +	'nodev' => N("Do not interpret character or block special devices on the filesystem."), + +	'noexec' => N("Do not allow execution of any binaries on the mounted +filesystem. This option might be useful for a server that has filesystems +containing binaries for architectures other than its own."), + +	'nosuid' => N("Do not allow set-user-identifier or set-group-identifier +bits to take effect. (This seems safe, but is in fact rather unsafe if you +have suidperl(1) installed.)"), + +	'ro' => N("Mount the filesystem read-only."), + +	'sync' => N("All I/O to the filesystem should be done synchronously."), + +	'users' => N("Allow every user to mount and umount the filesystem."),          + +	'user' => N("Allow an ordinary user to mount the filesystem."),          + +	'usrquota' => N("Enable user disk quota accounting, and optionally enforce limits"), + +        'user_xattr' => N("Support \"user.\" extended attributes"), + +        'umask=0' => N("Give write access to ordinary users"), + +        'umask=0022' => N("Give read-only access to ordinary users"), +    ); +} + + +sub rationalize { +    my ($part) = @_; + +    my ($options, $unknown) = &unpack($part); + +    if ($part->{fs_type} ne 'reiserfs') { +	$options->{notail} = 0; +    } +    if (!fs::type::can_be_one_of_those_fs_types($part, 'vfat', 'cifs', 'iso9660', 'udf')) { +	delete $options->{'codepage='}; +    } +    if (member($part->{mntpoint}, fs::type::directories_needed_to_boot())) { +	foreach (qw(users user noauto)) { +	    if ($options->{$_}) { +		$options->{$_} = 0; +		$options->{$_} = 0 foreach qw(nodev noexec nosuid); +	    } +	} +    } + +    &pack($part, $options, $unknown); +} + +sub set_default { +    my ($part, %opts) = @_; +    #- opts are: security iocharset codepage ignore_is_removable + +    my ($options, $unknown) = &unpack($part); + +    if (!$opts{ignore_is_removable} && $part->{is_removable}  +	  && !member($part->{mntpoint}, fs::type::directories_needed_to_boot())  +	  && (!$part->{fs_type} || $part->{fs_type} eq 'auto' || $part->{fs_type} =~ /:/)) { +	$part->{fs_type} = 'auto'; +	$options->{flush} = 1 if $part->{media_type} ne 'cdrom'; +    } + +    if ($part->{media_type} eq 'cdrom') { +	$options->{ro} = 1; +    } + +    if ($part->{media_type} eq 'fd') { +	# slow device so do not loose time, write now! +	$options->{flush} = 1; +    } + +    if (isTrueLocalFS($part)) { +	#- noatime on laptops (do not wake up the hd) +	#- otherwise relatime (wake up the hd less often / better performances) +	#- Do  not  update  inode  access times on this +	#- filesystem (e.g, for faster access  on  the +	#- news spool to speed up news servers). +	$options->{relatime} = $options->{noatime} = 0; +	$options->{ detect_devices::isLaptop() ? 'noatime' : 'relatime' } = 1 if !$opts{force_atime}; +    } +    if ($part->{fs_type} eq 'nfs') { +	put_in_hash($options, {  +			       nosuid => 1, 'rsize=8192,wsize=8192' => 1, soft => 1, +			      }); +    } +    if ($part->{fs_type} eq 'cifs') { +	add2hash($options, { 'username=' => '%' }) if !$options->{'credentials='}; +    } +    if (fs::type::can_be_this_fs_type($part, 'vfat')) { + +	put_in_hash($options, { +			       users => 1, noexec => 0, +			      }) if $part->{is_removable}; + +	put_in_hash($options, { +			       'umask=0' => $opts{security} <= 1 && !isESP($part), +			       'iocharset=' => $opts{iocharset}, 'codepage=' => $opts{codepage}, +			      }); +    } +    if ($part->{fs_type} eq 'ntfs') { +	put_in_hash($options, { ro => 1, 'nls=' => $opts{iocharset}, +				'umask=0' => $opts{security} < 1, 'umask=0022' => $opts{security} < 2, +			      }); +    } +    if (fs::type::can_be_this_fs_type($part, 'iso9660')) { +	put_in_hash($options, { users => 1, noexec => 0, 'iocharset=' => $opts{iocharset} }); +    } +    if ($part->{fs_type} eq 'reiserfs') { +	$options->{notail} = 1; +	$options->{user_xattr} = 1; +    } +    if (member($part->{fs_type}, qw(ext2 ext3 ext4))) { +	$options->{acl} = 1; +    } +    if (isLoopback($part) && !isSwap($part)) { #- no need for loop option for swap files +	$options->{loop} = 1; +    } + +    # rationalize: no need for user +    if ($options->{autofs}) { +	$options->{users} = $options->{user} = 0; +    } + +    if ($options->{user} || $options->{users}) { +        # have noauto when we have user +        $options->{noauto} = 1; +	# ensure security  (user_implies - noexec as noexec is not a security matter) +	$options->{$_} = 1 foreach 'nodev', 'nosuid'; +    } + +    &pack($part, $options, $unknown); + +    rationalize($part); +} + +sub set_all_default { +    my ($all_hds, %opts) = @_; +    #- opts are: security iocharset codepage + +    foreach my $part (fs::get::really_all_fstab($all_hds)) { +	set_default($part, %opts); +    } +} + +1; diff --git a/perl-install/fs/mount_point.pm b/perl-install/fs/mount_point.pm new file mode 100644 index 000000000..56f2405e9 --- /dev/null +++ b/perl-install/fs/mount_point.pm @@ -0,0 +1,136 @@ +package fs::mount_point; + +use diagnostics; +use strict; + +use common; +use any; +use fs::type; + +sub guess_mount_point { +    my ($part, $prefix, $user) = @_; + +    my %l = ( +	     '/'     => 'etc/fstab', +	     '/boot' => 'vmlinuz', +	     '/boot' => 'vmlinux', +	     '/boot' => 'uImage', +	     '/tmp'  => '.X11-unix', +	     '/usr'  => 'src', +	     '/var'  => 'spool', +	    ); + +    my $handle = any::inspect($part, $prefix) or return; +    my $d = $handle->{dir}; +    my $mnt = find { -e "$d/$l{$_}" } keys %l; +    $mnt ||= (stat("$d/.bashrc"))[4] ? '/root' : '/home/user' . ++$$user if -e "$d/.bashrc"; +    $mnt ||= (any { -d $_ && (stat($_))[4] >= 1000 && -e "$_/.bashrc" } glob_($d)) ? '/home' : ''; +    # Keep uid 500 here for guesswork, but base it on .bash_history to increase +    # changes it's a real user. +    $mnt ||= (any { -d $_ && (stat($_))[4] >= 500 && -e "$_/.bash_history" } glob_($d)) ? '/home' : ''; +    ($mnt, $handle); +} + +sub suggest_mount_points { +    my ($fstab, $prefix, $uniq) = @_; + +    my $user; +    foreach my $part (grep { isTrueFS($_) } @$fstab) { +	$part->{mntpoint} && !$part->{unsafeMntpoint} and next; #- if already found via an fstab + +	my ($mnt, $handle) = guess_mount_point($part, $prefix, \$user) or next; + +	next if $uniq && fs::get::mntpoint2part($mnt, $fstab); +	$part->{mntpoint} = $mnt; delete $part->{unsafeMntpoint}; + +	#- try to find other mount points via fstab +	fs::merge_info_from_fstab($fstab, $handle->{dir}, $uniq, 'loose') if $mnt eq '/'; +    } +    # reuse existing ESP under UEFI: +    my @ESP = if_(is_uefi(), grep { isESP($_) } @$fstab); +    if (@ESP) { +	$ESP[0]{mntpoint} = '/boot/EFI'; +	delete $ESP[0]{unsafeMntpoint}; +    } +    $_->{mntpoint} and log::l("suggest_mount_points: $_->{device} -> $_->{mntpoint}") foreach @$fstab; +} + +sub suggest_mount_points_always { +    my ($fstab) = @_; + +    my @ESP = if_(is_uefi(), grep { isESP($_) && maybeFormatted($_) && !$_->{is_removable} } @$fstab); +    if (@ESP) { +	$ESP[0]{mntpoint} = "/boot/EFI"; +    } +    my @win = grep { isnormal_Fat_or_NTFS($_) && !$_->{isMounted} && maybeFormatted($_) && !$_->{is_removable} } @$fstab; +    log::l("win parts: ", join ",", map { $_->{device} } @win) if @win; +    if (@win == 1) { +	$win[0]{mntpoint} = "/media/windows"; +    } else { +	my %w; foreach (@win) { +	    my $v = $w{$_->{device_windobe}}++; +	    $_->{mntpoint} = $_->{unsafeMntpoint} = "/media/win_" . lc($_->{device_windobe}) . ($v ? $v+1 : ''); #- lc cuz of StartOffice(!) cf dadou +	} +    } +} + +sub validate_mount_points { +    my ($fstab) = @_; + +    #- TODO: set the mntpoints + +    my %m; foreach (@$fstab) { +	my $m = $_->{mntpoint}; + +	$m && $m =~ m!^/! or next; #- there may be a lot of swaps or "none" + +	$m{$m} and die N("Duplicate mount point %s", $m); +	$m{$m} = 1; + +	#- in case the type does not correspond, force it to default fs (ext4 currently) +	fs::type::set_fs_type($_, defaultFS()) if !isTrueFS($_) && !isOtherAvailableFS($_); +    } +    1; +} + +sub ask_mount_points { +    my ($in, $fstab, $all_hds) = @_; + +    my @fstab = grep { isTrueFS($_) } @$fstab; +    @fstab = grep { isSwap($_) } @$fstab if @fstab == 0; +    @fstab = @$fstab if @fstab == 0; +    die N("No partition available") if @fstab == 0; + +    { +	my $_w = $in->wait_message('', N("Scanning partitions to find mount points")); +	suggest_mount_points($fstab, $::prefix, 'uniq'); +	log::l("default mntpoint $_->{mntpoint} $_->{device}") foreach @fstab; +    } +    if (@fstab == 1) { +	$fstab[0]{mntpoint} = '/'; +    } else { +	$in->ask_from_({ messages => N("Choose the mount points"), +			title => N("Partitioning"), +			interactive_help_id => 'ask_mntpoint_s', +			callbacks => { +			    complete => sub { +				require diskdrake::interactive; +				eval { 1, find_index { +				    !diskdrake::interactive::check_mntpoint($in, $_->{mntpoint}, $_, $all_hds); +				} @fstab }; +			    }, +			}, +		      }, +		      [ map {  +			  {  +			      label => partition_table::description($_),  +			      val => \$_->{mntpoint}, +			      not_edit => 0, +			      list => [ '', fsedit::suggestions_mntpoint(fs::get::empty_all_hds()) ], +			  }; +		        } @fstab ]) or return; +    } +    validate_mount_points($fstab); +} + +1; diff --git a/perl-install/fs/partitioning.pm b/perl-install/fs/partitioning.pm new file mode 100644 index 000000000..18fa7e114 --- /dev/null +++ b/perl-install/fs/partitioning.pm @@ -0,0 +1,83 @@ +package fs::partitioning; + +use diagnostics; +use strict; + +use common; +use fs::format; +use fs::get; +use fs::type; + +sub guess_partitions_to_format { +    my ($fstab) = @_; +    my $root_part = fs::get::root($fstab); +    foreach (@$fstab) { +	$_->{mntpoint} = "swap" if isSwap($_) && ($_->{rootDevice} eq $root_part->{rootDevice} || !$_->{is_removable} && !$root_part->{is_removable}); +	$_->{mntpoint} or next; + +	add2hash_($_, { toFormat => $_->{notFormatted} }) if $_->{fs_type}; #- eg: do not set toFormat for isRawRAID (0xfd) +        $_->{toFormatUnsure} ||= member($_->{mntpoint}, '/', '/usr'); + +	if (!$_->{toFormat}) { +	    my $fs_type = fs::type::fs_type_from_magic($_); +	    if (!$fs_type || $fs_type ne $_->{fs_type}) { +		log::l("setting toFormatUnsure for $_->{device} because <$_->{fs_type}> ne <$fs_type>"); +		$_->{toFormatUnsure} = 1; +	    } +	} +    } +} + +sub choose_partitions_to_format { +    my ($in, $fstab) = @_; + +    guess_partitions_to_format($fstab); + +    my @l = grep { !$_->{isMounted} && $_->{mntpoint} && !isSwap($_) && +		   (!isFat_or_NTFS($_) || $_->{notFormatted}) && +		   (!isOtherAvailableFS($_) || $_->{toFormat}); +	       } @$fstab; +    $_->{toFormat} = 1 foreach grep { isSwap($_) } @$fstab; + +    return if @l == 0 || every { $_->{toFormat} } @l; + +    #- keep it temporary until the guy has accepted +    $_->{toFormatTmp} = $_->{toFormat} || $_->{toFormatUnsure} foreach @l; + +    $in->ask_from_( +        { messages => N("Choose the partitions you want to format"), +	  interactive_help_id => 'formatPartitions', +          advanced_messages => N("Check for bad blocks?"), +        }, +        [ map {  +	    my $e = $_; +	    ({ +	      text => partition_table::description($e), type => 'bool', +	      val => \$e->{toFormatTmp} +	     }, if_(!isLoopback($_) && isBlockCheckable($_), { +	      text => partition_table::description($e), type => 'bool', advanced => 1,  +	      disabled => sub { !$e->{toFormatTmp} }, +	      val => \$e->{toFormatCheck} +        })) } @l ] +    ) or die 'already displayed'; +    #- ok now we can really set toFormat +    foreach (@l) { +	$_->{toFormat} = delete $_->{toFormatTmp}; +	set_isFormatted($_, 0); +    } +} + +sub format_mount_partitions { +    my ($in, $all_hds, $fstab) = @_; +    my ($w, $wait_message) = $in->wait_message_with_progress_bar; +    catch_cdie { +        fs::format::formatMount_all($all_hds, $fstab, $wait_message); +    } sub {  +	$@ =~ /fsck failed on (\S+)/ or return; +	$in->ask_yesorno('', N("Failed to check filesystem %s. Do you want to repair the errors? (beware, you can lose data)", $1), 1); +    }; +    undef $w; #- help perl (otherwise wait_message stays forever in curses) +    die N("Not enough swap space to fulfill installation, please add some") if availableMemory() < 40 * 1024; +} + +1; diff --git a/perl-install/fs/partitioning_wizard.pm b/perl-install/fs/partitioning_wizard.pm new file mode 100644 index 000000000..6e77f3eff --- /dev/null +++ b/perl-install/fs/partitioning_wizard.pm @@ -0,0 +1,655 @@ +package fs::partitioning_wizard; + +use diagnostics; +use strict; +use utf8; + +use common; +use devices; +use fsedit; +use fs::type; +use fs::mount_point; +use partition_table; +use partition_table::raw; +use partition_table::dos; +use POSIX qw(ceil); + + +=head1 SYNOPSYS + +B<fs::partitioning_wizard> implements the partitioning wizard. + +=head1 Functions + +=over + +=item from_Mb($mb, $min, $max) + +This function is used to convert back to sectors count the size of +a partition ($mb) given from the interface (on Resize or Create). +modified to take into account a true bounding with min and max. +Unit of $mb is mega bytes, min and max are in sectors. + +=cut + +sub from_Mb { +    emy ($mb, $min, $max) = @_; +    $mb <= to_Mb($min) and return $min; +    $mb >= to_Mb($max) and return $max; +    MB($mb); +} +sub to_Mb { +    my ($size_sector) = @_; +    to_int($size_sector / 2048); +} + +sub partition_with_diskdrake { +    my ($in, $all_hds, $fstab, $manual_fstab, $_partitions, $partitioning_flags, $skip_mtab) = @_; +    my $ok; + +    # The classic installer sets $skip_mtab to either undef or 1. The live +    # installer sets it to 'skip_mtab'. If $skip_mtab is not set, this has +    # already been done by fs::any::get_hds. +    if ($skip_mtab eq 'skip_mtab') { +        fs::mount_point::suggest_mount_points_always($fstab); +    } + +    do { +	$ok = 1; +	my $do_force_reload = sub { +            require File::Temp; +            require fs::dmcrypt; +            my (undef, $tmp_file) = File::Temp::mkstemp('/tmp/crypttab.XXXXXXX'); +            fs::dmcrypt::save_crypttab_($all_hds, $tmp_file); +            my $new_hds = fs::get::empty_all_hds(); +            fs::any::get_hds($new_hds, $fstab, $manual_fstab, $partitioning_flags, $skip_mtab, $in); +            %$all_hds = %$new_hds; +            fs::dmcrypt::read_crypttab_($all_hds, $tmp_file); +            rm_rf($tmp_file); +            $all_hds; +	}; +	require diskdrake::interactive; +	{ +	    local $::expert = 0; +	    diskdrake::interactive::main($in, $all_hds, $do_force_reload); +	} +	my @fstab = fs::get::fstab($all_hds); + +	unless (fs::get::root_(\@fstab)) { +	    $ok = 0; +	    $in->ask_okcancel(N("Partitioning"), N("You must have a root partition. +To accomplish this, create a partition (or click on an existing one). +Then choose action ``Mount point'' and set it to `/'"), 1) or return; +	} + +	if (!any { isSwap($_) } @fstab) { +	    $ok &&= $in->ask_okcancel('', N("You do not have a swap partition.\n\nContinue anyway?")); +	} +	if (is_uefi()) { +	  my $part = fs::get::has_mntpoint("/boot/EFI", $all_hds); +	  if (!$part || !isESP($part)) { +	    $in->ask_warn('', N("You must have a ESP FAT32 partition mounted in /boot/EFI")); +	    $ok = ''; +	  } +	} else { +	    if (fs::any::is_boot_bios_part_needed($all_hds)) { +		$in->ask_warn('', N("You must have a BIOS boot partition for non-UEFI GPT-partitioned disks. Please create one before continuing.")); +		$ok = ''; +	    } +	} +    } until $ok; +    1; +} + +sub partitionWizardSolutions { +    my ($in, $all_hds, $all_fstab, $manual_fstab, $partitions, $partitioning_flags, $skip_mtab, $o_target) = @_; +    my $hds = $all_hds->{hds}; +    my $fstab; +    my $full_fstab = [ fs::get::fstab($all_hds) ]; +    if ($o_target) { +        $hds = [ $o_target ]; +        $fstab = [ grep { $_->{rootDevice} eq $o_target->{device} } fs::get::fstab($all_hds) ]; +    } else { +        $fstab = $full_fstab; +    } + +    my @wizlog; +    my (%solutions); + +    my $min_linux = MB(600); +    my $min_swap = MB(50); +    my $min_freewin = MB(100); +    fsedit::init_mntpnt_suggestions($all_hds, $o_target); + +    # each solution is a [ score, text, function ], where the function retunrs true if succeeded + +    my @hds_rw = grep { !$_->{readonly} } @$hds; +    my @hds_can_add = grep { $_->{type} ne 'hd' || $_->can_add } @hds_rw; +    if (fs::get::hds_free_space(@hds_can_add) > $min_linux) { +	$solutions{free_space} = [ 30, N("Use free space"), sub { fsedit::auto_allocate($all_hds, $partitions, $o_target); 1 } ]; +    } else {  +	push @wizlog, N("Not enough free space to allocate new partitions") . ": " . +	  (@hds_can_add ?  +	   fs::get::hds_free_space(@hds_can_add) . " < $min_linux" : +	   "no harddrive on which partitions can be added"); +    } + +    if (my @truefs = grep { isTrueLocalFS($_) } @$fstab) { +	#- value twice the ext2 partitions +	$solutions{existing_part} = [ 20 + @truefs + @$fstab, N("Use existing partitions"), sub { fs::mount_point::ask_mount_points($in, $full_fstab, $all_hds) } ]; +    } else { +	push @wizlog, N("There is no existing partition to use"); +    } + +    if (my @ok_for_resize_fat = grep { isnormal_Fat_or_NTFS($_) && !fs::get::part2hd($_, $all_hds)->{readonly} +					 && $_->{size} > $min_linux + $min_swap + $min_freewin } @$fstab) { +        @ok_for_resize_fat = map { +            my $part = $_; +            my $hd = fs::get::part2hd($part, $all_hds); +            my $resize_fat = eval { +                my $pkg = $part->{fs_type} eq 'vfat' ? do {  +                    require resize_fat::main; +                    'resize_fat::main'; +                } : do { +                    require diskdrake::resize_ntfs; +                    'diskdrake::resize_ntfs'; +                }; +                $pkg->new($part->{device}, devices::make($part->{device})); +            }; +            if ($@) { +                log::l("The FAT resizer is unable to handle $part->{device} partition%s", formatError($@)); +                undef $part; +            } +            if ($part) { +                my $min_win = eval { +                    my $_w = $in->wait_message(N("Resizing"), N("Computing the size of the Microsoft Windows® partition")); +                    $resize_fat->min_size + $min_freewin; +                }; +                if ($@) { +                    log::l("The FAT resizer is unable to get minimal size for $part->{device} partition %s", formatError($@)); +                    undef $part; +                } else { +                    my $min_linux_all = $min_linux + $min_swap; +                    #- make sure that even after normalizing the size to cylinder boundaries, the minimun will be saved, +                    #- this save at least a cylinder (less than 8Mb). +                    $min_win += partition_table::raw::cylinder_size($hd); + +                    if ($part->{size} <= $min_linux_all + $min_win) { +#                die N("Your Microsoft Windows® partition is too fragmented. Please reboot your computer under Microsoft Windows®, run the ``defrag'' utility, then restart the %s installation.", N("Mageia")); +                        undef $part; +                    } else { +                        $part->{resize_fat} = $resize_fat; +                        $part->{min_win} = $min_win; +                        $part->{min_linux} = $min_linux_all; +                        #- try to keep at least 1GB free for Windows +                        #- try to use from 20GB to 20% free space for Linux +                        my $suggested_size = max( +                            $part->{min_win} + 1 * MB(1024), +                            min( +                                $part->{size} - int(0.2 * ($part->{size} - $part->{min_win})), +                                $part->{size} - 20 * MB(1024), +                            ), +                        ); +                        $part->{req_size} = max(min($suggested_size, $part->{size} - $part->{min_linux}), $part->{min_win}); +                    } +                } +            } +            $part || (); +        } @ok_for_resize_fat; +	if (@ok_for_resize_fat) { +            $solutions{resize_fat} = +                [ 20 - @ok_for_resize_fat, N("Use the free space on a Microsoft Windows® partition"), +                  sub { +                      my $part; +                      if (!$in->isa('interactive::gtk')) { +                          $part = $in->ask_from_listf_raw({ messages => N("Which partition do you want to resize?"), +                                                               interactive_help_id => 'resizeFATChoose', +                                                             }, \&partition_table::description, \@ok_for_resize_fat) or return; +                          $part->{size} > $part->{min_linux} + $part->{min_win} or die N("Your Microsoft Windows® partition is too fragmented. Please reboot your computer under Microsoft Windows®, run the ``defrag'' utility, then restart the %s installation.", N("Mageia")); +                      } else { +                          my @selected = grep { +                              $_->{selected_for_resize} && +                              $o_target->{device} eq $_->{rootDevice}; # Not needed but let's be safe +                          } @ok_for_resize_fat; +                          my $nb_parts = @selected; +                          die N("Failed to find the partition to resize (%d choices)", $nb_parts) unless $nb_parts == 1; +                          $part = $selected[0]; +                      } +                      my $resize_fat = $part->{resize_fat}; +                      my $hd = fs::get::part2hd($part, $all_hds); +                      $in->ask_okcancel('', formatAlaTeX( +                                            #-PO: keep the double empty lines between sections, this is formatted a la LaTeX +                                            N("WARNING! + + +Your Microsoft Windows® partition will be now resized. + + +Be careful: this operation is dangerous. If you have not already done so, you first need to exit the installation, run \"chkdsk c:\" from a Command Prompt under Microsoft Windows® (beware, running graphical program \"scandisk\" is not enough, be sure to use \"chkdsk\" in a Command Prompt!), optionally run defrag, then restart the installation. You should also backup your data. + + +When sure, press %s.", N("Next")))) or return; + +                      my $oldsize = $part->{size}; +                      if (!$in->isa('interactive::gtk')) { +                          my $mb_size = to_Mb($part->{req_size}); +                          my $max_win = $part->{size} - $part->{min_linux}; +                          $in->ask_from(N("Partitionning"), N("Which size do you want to keep for Microsoft Windows® on partition %s?", partition_table::description($part)), [ +                                        { label => N("Size"), val => \$mb_size, min => to_Mb($part->{min_win}), max => to_Mb($max_win), type => 'range' }, +                                    ]) or return; +                          $part->{req_size} = from_Mb($mb_size, $part->{min_win}, $part->{max_win}); +                      } +                      $part->{size} = $part->{req_size}; + +                      $hd->adjustEnd($part); + +                      eval { +                          my $_w = $in->wait_message(N("Resizing"), N("Resizing Microsoft Windows® partition")); +                          $resize_fat->resize($part->{size}); +                      }; +                      if (my $err = $@) { +                          $part->{size} = $oldsize; +                          die N("FAT resizing failed: %s", formatError($err)); +                      } + +                      $in->ask_warn('', N("To ensure data integrity after resizing the partition(s), +filesystem checks will be run on your next boot into Microsoft Windows®")) if $part->{fs_type} ne 'vfat'; + +                      set_isFormatted($part, 1); +                      partition_table::will_tell_kernel($hd, resize => $part); #- down-sizing, write_partitions is not needed +                      partition_table::adjust_local_extended($hd, $part); +                      partition_table::adjust_main_extended($hd); + +                      fsedit::auto_allocate($all_hds, $partitions, $hd); +                      1; +                  }, \@ok_for_resize_fat ]; +        } +    } else { +	push @wizlog, N("There is no FAT partition to resize (or not enough space left)"); +    } + +    if (@$fstab && @hds_rw) { +	$solutions{wipe_drive} = +	  [ 10, fsedit::is_one_big_fat_or_NT($hds) ? N("Remove Microsoft Windows®") : N("Erase and use entire disk"), +	    sub { +                my $hd; +                if (!$in->isa('interactive::gtk')) { +                    $hd = $in->ask_from_listf_raw({ messages => N("You have more than one hard disk drive, which one do you want the installer to use?"), +                                                       title => N("Partitioning"), +                                                       interactive_help_id => 'takeOverHdChoose', +                                                     }, +                                                     \&partition_table::description, \@hds_rw) or return; +                } else { +                    $hd = $o_target; +                } +		$in->ask_okcancel_({ messages => N("ALL existing partitions and their data will be lost on drive %s", partition_table::description($hd)), +				    title => N("Partitioning"), +				    interactive_help_id => 'takeOverHdConfirm' }) or return; +		fsedit::partition_table_clear_and_initialize($all_hds->{lvms}, $hd, $in); +		# FIXME: reread all_hds: +		# re add suggestions if needed (as we might just have erased eg Boot BIOS partition): +		fsedit::init_mntpnt_suggestions($all_hds, $hd, 1); +		fsedit::auto_allocate($all_hds, $partitions, $hd); +		1; +	    } ]; +    } + +    if (@hds_rw || find { $_->isa('partition_table::lvm') } @$hds) { +	$solutions{diskdrake} = [ 0, N("Custom disk partitioning"), sub { +	    partition_with_diskdrake($in, $all_hds, $all_fstab, $manual_fstab, $partitions, $partitioning_flags, $skip_mtab); +        } ]; +    } + +    $solutions{fdisk} = +      [ -10, N("Use fdisk"), sub { +	    $in->enter_console; +	    foreach (@$hds) { +		print "\n" x 10, N("You can now partition %s. +When you are done, do not forget to save using `w'", partition_table::description($_)); +		print "\n\n"; +		my $pid = 0; +		$pid = fork() or exec "fdisk", devices::make($_->{device}); +		waitpid($pid, 0); +	    } +	    $in->leave_console; +	    0; +	} ] if $partitioning_flags->{fdisk}; + +    log::l("partitioning wizard log:\n", (map { ">>wizlog>>$_\n" } @wizlog)); +    %solutions; +} + +sub warn_reboot_needed { +    my ($in) = @_; +    $in->ask_warn(N("Partitioning"), N("You need to reboot for the partition table modifications to take effect")); +} + +sub create_display_box { +    my ($kind, $resize, $fill_empty, $button) = @_; + +    # Hide empty space < 2MB, wehave such holes due to alignment +    my @parts = grep { $_->{size} > MB(2) || !isEmpty($_) } diskdrake::hd_gtk::kind2parts($kind); + +    my $totalsectors = diskdrake::hd_gtk::kind2sectors($kind, @parts); + +    my $width = 520; +    my $minwidth = 40; + +    my $display_box = ugtk3::gtkset_size_request(Gtk3::HBox->new(0,0), -1, 26); +    $display_box->set_spacing(1); + +    my $sep_count = @parts - 1; +    #- ratio used to compute initial partition pixel width (each partition should be > min_width) +    #- though, the pixel/sectors ratio cannot be the same for all the partitions +    my $initial_ratio = $totalsectors ? ($width - @parts * $minwidth - $sep_count) / $totalsectors : 1; + +    my $vbox = Gtk3::VBox->new; + +    my $desc; + +    if ($resize) { +	my %resizable_parts; +	foreach my $entry (@$resize) { +	    # selected_for_resize may have been set on another disk, clear it +	    $entry->{selected_for_resize} = 0; +	    $resizable_parts{$entry->{device}} = $entry; +	} +	# find resizable parts on this disk +	my @choices = grep { $resizable_parts{$_->{device}} } @parts; +	my @sorted_resize = sort { +		$a->{size} - $a->{req_size} <=> $b->{size} - $b->{req_size}; +	    } @choices; +	$sorted_resize[-1]{selected_for_resize} = 1; +    } + +    foreach my $entry (@parts) { +	my $part_info = Gtk3::Label->new($entry->{device_LABEL}); +	my @colorized_fs_types = qw(ext2 ext3 ext4 xfs swap vfat ntfs ntfs-3g); +        my $part_widget = Gtk3::EventBox->new; +        $entry->{width} = int($entry->{size} * $initial_ratio) + $minwidth; +        if ($resize && $entry->{selected_for_resize}) { +            my $ratio; +            my $update_ratio = sub { $ratio = $entry->{width} / $entry->{size} }; +            $update_ratio->(); + +            $part_widget->set_name("PART_vfat"); +            $part_info->set_size_request(ceil($ratio * $entry->{min_win}), 0); + +            my $mdv_widget = gtkadd(gtkset_name(Gtk3::EventBox->new, "PART_new"), +                                    gtkset_size_request(gtknew("Image", file => "small-logo"), +                                                        $ratio * MB(600), 0)); + +            my $hpane = Gtk3::HPaned->new; +            $hpane->pack1($part_widget, 1, 0); +            $hpane->pack2($mdv_widget, 1, 0); +            $hpane->set_position(ceil($ratio * $entry->{req_size})); +            ugtk3::gtkset_size_request($hpane, $entry->{width}, 0); +            ugtk3::gtkpack__($display_box, $hpane); + +            my $add_part_size_info = sub { +                my ($name, $label) = @_; +                ugtk3::gtkpack__($desc, +                                 gtkadd(gtkset_name(Gtk3::EventBox->new, $name), +                                        Gtk3::Label->new(" " x 4)), +                                 gtkset_size_request(gtkset_alignment($label, 0, 0.5), +                                                     150, 20)); +            }; +            $desc = Gtk3::HBox->new(0,0); + +            my $win_size_label = Gtk3::Label->new; +            $add_part_size_info->("PART_vfat", $win_size_label); + +            my $mdv_size_label = Gtk3::Label->new; +            $add_part_size_info->("PART_new", $mdv_size_label); + +            my $update_size_labels = sub { +                $win_size_label->set_label(" Windows (" . formatXiB($entry->{req_size}, 512) . ")"); +                $mdv_size_label->set_label(" Mageia (" . formatXiB($entry->{size} - $entry->{req_size}, 512) . ")"); +                0; +            }; +            my $update_req_size = sub { +                $entry->{req_size} = int($hpane->get_position / $ratio); +                $update_size_labels->(); +            }; +            my $button_activate = sub { +                $button->activate; +                0; +            }; +            $hpane->signal_connect('size-allocate' => sub { +                my (undef, $alloc) = @_; +                $entry->{width} = $alloc->{width}; +                $update_ratio->(); +                0; +            }); +            $update_size_labels->(); +            $hpane->signal_connect('motion-notify-event' => $update_req_size); +            $hpane->signal_connect('move-handle' => $update_req_size); +            $hpane->signal_connect('button-press-event' => $button_activate); +            $vbox->signal_connect('button-press-event' => $button_activate); +            $button->signal_connect('focus-in-event' => sub { +                $hpane->grab_focus; +                0; +            }); +        } else { +            if ($fill_empty && isEmpty($entry)) { +                $part_info = gtknew("Image", file => "small-logo"); +                $part_widget->set_name("PART_new"); +            } else { +                $part_widget->set_name("PART_" . (isEmpty($entry) ? 'empty' : +                                         $entry->{fs_type} && member($entry->{fs_type}, @colorized_fs_types) ? $entry->{fs_type} : +                                         'other')); +            } +            $part_widget->set_size_request($entry->{width}, 0); +            ugtk3::gtkpack($display_box, $part_widget); +        } +	$part_widget->add($part_info); +    } +    unless ($resize || $fill_empty) { +        my @types = (N_("Ext2/3/4"), N_("XFS"), N_("Swap"), N_("Windows"), +                    N_("Other"), N_("Empty")); +        my %name2fs_type = ('Ext2/3/4' => 'ext3', 'XFS' => 'xfs', Swap => 'swap', Other => 'other', "Windows" => 'vfat', HFS => 'hfs'); +        $desc = ugtk3::gtkpack(Gtk3::HBox->new, +                map { +                     my $t = $name2fs_type{$_}; +                     my $ev = Gtk3::EventBox->new; +		     my $w = Gtk3::Label->new(translate($_)); +	             $ev->add($w); +		     $ev->set_name('PART_' . ($t || 'empty')); +                     $ev; +                } @types); +    } + +    $vbox->add($display_box); +    $vbox->add($desc) if $desc; + +    $vbox; +} + +sub display_choices { +    my ($o, $contentbox, $mainw, %solutions) = @_; +    my @solutions = sort { $solutions{$b}[0] <=> $solutions{$a}[0] } keys %solutions; +    my @sol = grep { $solutions{$_}[0] >= 0 } @solutions; + +    log::l(''  . "solutions found: " . join(', ', map { $solutions{$_}[1] } @sol) . +           " (all solutions found: " . join(', ', map { $solutions{$_}[1] } @solutions) . ")"); + +    @solutions = @sol if @sol > 1; +    log::l("solutions: ", int @solutions); +    @solutions or $o->ask_warn(N("Partitioning"), N("I cannot find any room for installing")), die 'already displayed'; + +    log::l('HERE: ', join(',', map { $solutions{$_}[1] } @solutions)); + +    $contentbox->foreach(sub { $contentbox->remove($_[0]) }); + +    $mainw->{kind}{display_box} ||= create_display_box($mainw->{kind}); +    ugtk3::gtkpack2__($contentbox, $mainw->{kind}{display_box}); +    ugtk3::gtkpack__($contentbox, gtknew('Label', +                                         text => N("The DrakX Partitioning wizard found the following solutions:"), +                                         alignment => [0, 0])); + +    my $choicesbox = gtknew('VBox'); +    my $oldbutton; +    my $sep; +    foreach my $s (@solutions) { +        my $item; +        my $vbox = gtknew('VBox'); +        my $button = gtknew('RadioButton', child => $vbox); +        if ($s eq 'free_space') { +            $item = create_display_box($mainw->{kind}, undef, 1); +        } elsif ($s eq 'resize_fat') { +            $item = create_display_box($mainw->{kind}, $solutions{$s}[3], undef, $button); +        } elsif ($s eq 'existing_part') { +        } elsif ($s eq 'wipe_drive') { +            $item = Gtk3::EventBox->new; +            my $b2 = gtknew("Image", file => "small-logo"); +            $item->add($b2); +            $item->set_size_request(520,26); +            $item->set_name("PART_new"); +        } elsif ($s eq 'diskdrake') { +        } else { +            log::l($s); +            next; +        } +        ugtk3::gtkpack($vbox,  +                       gtknew('Label', +                              text => $solutions{$s}[1], +                              alignment => [0, 0])); +        ugtk3::gtkpack($vbox, $item) if defined($item); +        $button->join_group($oldbutton) if $oldbutton; +        $oldbutton = $button; +        $button->signal_connect('toggled', sub { $mainw->{sol} = $solutions{$s} if $_[0]->get_active }); +        ugtk3::gtkpack2__($choicesbox, $button); +        $sep = gtknew('HSeparator'); +        ugtk3::gtkpack2__($choicesbox, $sep); +    } +    $choicesbox->remove($sep); +    ugtk3::gtkadd($contentbox, $choicesbox); +    $mainw->{sol} = $solutions{@solutions[0]}; +} + +sub main { +    my ($o, $all_hds, $fstab, $manual_fstab, $partitions, $partitioning_flags, $skip_mtab, $b_nodiskdrake) = @_; + +    my $sol; + +    if ($o->isa('interactive::gtk')) { +        require mygtk3; +        mygtk3->import(qw(gtknew)); +        require ugtk3; +        ugtk3->import(qw(:wrappers)); + +        my $mainw = ugtk3->new(N("Partitioning"), %$o, if__($::main_window, transient => $::main_window)); +        $mainw->{box_allow_grow} = 1; + +        mygtk3::set_main_window_size($mainw->{rwindow}); + +        require diskdrake::hd_gtk; +        diskdrake::hd_gtk::load_theme(); + +        my $mainbox = Gtk3::VBox->new; + +        my @kinds = map { diskdrake::hd_gtk::hd2kind($_) } sort { $a->{is_removable} <=> $b->{is_removable} } @{ $all_hds->{hds} }; +        #push @kinds, diskdrake::hd_gtk::raid2real_kind($_) foreach @{$all_hds->{raids}}; +        push @kinds, map { diskdrake::hd_gtk::lvm2kind($_) } @{$all_hds->{lvms}}; + +        my $hdchoice = Gtk3::HBox->new; + +        my $hdchoicelabel = Gtk3::Label->new(N("Here is the content of your disk drive ")); + +        my $combobox = Gtk3::ComboBoxText->new; +        foreach (@kinds) { +            my $info = $_->{val}{info} || $_->{val}{device}; +            $info =~ s|^(?:.*/)?(.{24}).*|$1|; +            $info .= " (" . formatXiB($_->{val}{totalsectors}, 512) . ")" if $_->{val}{totalsectors}; +            $combobox->append_text($info); +        } +        $combobox->set_active(0); + +        ugtk3::gtkpack2__($hdchoice, $hdchoicelabel); +        $hdchoice->add($combobox); + +        ugtk3::gtkpack2__($mainbox, $hdchoice); + +        my $contentbox = Gtk3::VBox->new(0, 12); + +        my $scroll = Gtk3::ScrolledWindow->new; +        $scroll->set_policy('automatic', 'automatic'), +        my $vp = Gtk3::Viewport->new; +        $vp->set_shadow_type('none'); +        $vp->add($contentbox); +        $scroll->add($vp); +        $mainbox->add($scroll); + +        my $kind = $kinds[$combobox->get_active]; +        my %solutions = partitionWizardSolutions($o, $all_hds, $fstab, $manual_fstab, $partitions, $partitioning_flags, $skip_mtab, diskdrake::hd_gtk::kind2hd($kind)); +        delete $solutions{diskdrake} if $b_nodiskdrake; +        $mainw->{kind} = $kind; +        display_choices($o, $contentbox, $mainw, %solutions); + +        $combobox->signal_connect("changed", sub {         +            my $curr = $kinds[$combobox->get_active]; +            return if !$curr; +            $mainw->{kind} = $curr; +            my %solutions = partitionWizardSolutions($o, $all_hds, $fstab, $manual_fstab, $partitions, $partitioning_flags, $skip_mtab, diskdrake::hd_gtk::kind2hd($mainw->{kind})); +            delete $solutions{diskdrake} if $b_nodiskdrake; +            display_choices($o, $contentbox, $mainw, %solutions); +            $mainw->{window}->show_all; +        }); + +        my @more_buttons = ( +            if_($::isInstall,  +            [ gtknew('Install_Button', +                     text => N("Help"), +                     clicked => sub { interactive::gtk::display_help($o, {interactive_help_id => 'doPartitionDisks' }) }), +              undef, 1 ]), +            ); +        my $buttons_pack = $mainw->create_okcancel(N("Next"), undef, '', @more_buttons); +        $mainbox->pack_end($buttons_pack, 0, 0, 0); +        ugtk3::gtkadd($mainw->{window}, $mainbox); +        $mainw->{window}->show_all; + +        $mainw->main; + +        $sol=$mainw->{sol}; +    } else { +        my %solutions = partitionWizardSolutions($o, $all_hds, $fstab, $manual_fstab, $partitions, $partitioning_flags, $skip_mtab); + +        delete $solutions{diskdrake} if $b_nodiskdrake; + +        my @solutions = sort { $b->[0] <=> $a->[0] } values %solutions; + +        my @sol = grep { $_->[0] >= 0 } @solutions; +        log::l(''  . "solutions found: " . join(', ', map { $_->[1] } @sol) .  +               " (all solutions found: " . join(', ', map { $_->[1] } @solutions) . ")"); +        @solutions = @sol if @sol > 1; +        log::l("solutions: ", int @solutions); +        @solutions or $o->ask_warn(N("Partitioning"), N("I cannot find any room for installing")), die 'already displayed'; +        log::l('HERE: ', join(',', map { $_->[1] } @solutions)); +        $o->ask_from_({  +            title => N("Partitioning"), +            interactive_help_id => 'doPartitionDisks', +                      }, +                      [ +                       { label => N("The DrakX Partitioning wizard found the following solutions:"),  title => $::isInstall }, +                       { val => \$sol, list => \@solutions, format => sub { $_[0][1] }, type => 'list' }, +                      ]); +    } +    log::l("partitionWizard calling solution $sol->[1]"); +    my $ok = eval { $sol->[2]->() }; +    if (my $err = $@) { +        if ($err =~ /wizcancel/) { +            $_->destroy foreach $::WizardTable->get_children; +        } else { +            log::l("Partitioning failed: $err"); +            $o->ask_warn('', N("Partitioning failed: %s", formatError($err))); +        } +    } +    $ok or goto &main; +    1; +} + +=back + +=cut + +1; diff --git a/perl-install/fs/proc_partitions.pm b/perl-install/fs/proc_partitions.pm new file mode 100644 index 000000000..ea714ca5b --- /dev/null +++ b/perl-install/fs/proc_partitions.pm @@ -0,0 +1,82 @@ +package fs::proc_partitions; + +use common; + + +sub read_raw() { +    my (undef, undef, @all) = cat_("/proc/partitions"); +    grep { +	$_->{size} != 1 &&	      # skip main extended partition +	$_->{size} != 0x3fffffff &&   # skip cdroms (otherwise stops cd-audios) +	$_->{dev} !~ /mmcblk\d+[^p]/; # only keep partitions like mmcblk0p0 +	                              # not mmcblk0rpmb or mmcblk0boot0 as they +				      # are not in the partition table and +				      # things will break (mga#15759) +    } map {  +	my %l;  +	@l{qw(major minor size dev)} = split;  +	\%l; +    } @all; +} + +sub read { +    my ($hds, $o_ignore_fstype) = @_; + +    my @all = read_raw(); +    my ($parts, $_disks) = partition { $_->{dev} =~ /\d$/ && $_->{dev} !~ /^(sr|scd)/ } @all; + +    fs::get_major_minor($hds); + +    my $prev_part; +    foreach my $part (@$parts) { +	my $dev = $part->{dev}; +	if (my $hd = find { $part->{dev} =~ /^\Q$_->{device}\E./ } @$hds) { +	    put_in_hash($part, partition_table::hd2minimal_part($hd)); +	} +	 +	undef $prev_part if $prev_part && ($prev_part->{rootDevice} || '') ne ($part->{rootDevice} || ''); + +	$part->{device} = $dev; +	$part->{size} *= 2;	# from KB to sectors +	$part->{start} = $prev_part ? $prev_part->{start} + $prev_part->{size} : 0; +	require fs::type; +	put_in_hash($part, fs::type::type_subpart_from_magic($part)) if !$o_ignore_fstype; +	$prev_part = $part; +	delete $part->{dev}; # cleanup +    } +    @$parts; +} + +sub compare { +    my ($hd) = @_; + +    eval { $hd->isa('partition_table::lvm') } and return; + + +    my @l1 = partition_table::get_normal_parts($hd); +    my @l2 = grep { $_->{rootDevice} eq $hd->{device} } &read([$hd], 1); + +    #- /proc/partitions includes partition with type "empty" and a non-null size +    #- so add them for comparison +    my ($len1, $len2) = (int(@l1) + $hd->{primary}{nb_special_empty}, int(@l2)); + +    if ($len1 != $len2) { +	if (find { $_->{pt_type} == 0xbf } @l1) { +	    log::l("not using /proc/partitions because of the presence of solaris extended partition"); #- cf #33866 +	} else { +	    die sprintf( +		    "/proc/partitions does not agree with drakx %d != %d for %s:\n%s\n", $len1, $len2, $hd->{device}, +		    "/proc/partitions: " . join(", ", map { "$_->{device} ($_->{rootDevice})" } @l2)); +	} +    } +    $len2; +} + +sub use_ { +    my ($hd) = @_; + +    require partition_table::readonly; +    partition_table::readonly->initialize($hd, [ grep { $_->{rootDevice} eq $hd->{device} } &read([$hd]) ]); +} + +1; diff --git a/perl-install/fs/remote.pm b/perl-install/fs/remote.pm new file mode 100644 index 000000000..ea5944a25 --- /dev/null +++ b/perl-install/fs/remote.pm @@ -0,0 +1,45 @@ +package fs::remote; + +use strict; +use diagnostics; + +use fs::mount_options; + + +sub new {  +    my ($class, $o_v) = @_; +    bless($o_v || {}, $class); +} + +sub server_to_string { +    my ($_class, $server) = @_; +    $server->{name} || $server->{ip}; +} +sub comment_to_string { +    my ($_class, $comment) = @_; +    $comment; +} +sub to_dev { +    my ($class, $e) = @_; +    $class->to_dev_raw($class->server_to_string($e->{server}), $e->{name} || $e->{ip}); +} +sub to_string { +    my ($class, $e) = @_; +    my $comment = $class->comment_to_string($e->{comment}); +    ($e->{name} || $e->{ip}) . ($comment ? " ($comment)" : ''); +} + +sub to_fullstring { +    my ($class, $e) = @_; +    my $comment = $class->comment_to_string($e->{comment}); +    $class->to_dev($e) . ($comment ? " ($comment)" : ''); +} +sub to_fstab_entry_raw { +    my ($class, $e, $fs_type) = @_; +    my $fs_entry = { device => $class->to_dev($e), fs_type => $fs_type }; +    fs::mount_options::set_default($fs_entry); +    $fs_entry; +} + +1; + diff --git a/perl-install/fs/remote/davfs.pm b/perl-install/fs/remote/davfs.pm new file mode 100644 index 000000000..890530cb9 --- /dev/null +++ b/perl-install/fs/remote/davfs.pm @@ -0,0 +1,99 @@ +package fs::remote::davfs; + +use strict; +use diagnostics; + +use common; +use fs::mount_options; + +sub secrets_file() { "$::prefix/etc/davfs2/secrets" } + +sub fstab_entry_to_credentials { +    my ($part) = @_;     + +    my ($options, $unknown) = fs::mount_options::unpack($part); +    my %h = map { $_ => delete $options->{"$_="} } qw(username password); +    foreach (qw(username password)) { +        $h{$_} ||= 'nobody'; +    } +    $h{mntpoint} = $part->{mntpoint} or return; +    fs::mount_options::pack_($part, $options, $unknown), \%h; +} + +sub save_credentials { +    my ($credentials) = @_; +    @$credentials or return; + +    output_with_perm(secrets_file(), 0600,  +		     map { to_double_quoted($_->{mntpoint}, $_->{username}, $_->{password}) . "\n" } @$credentials); +} + +sub mountpoint_credentials_save { +    my ($mntpoint, $mount_opt) = @_; +    my @entries = read_credentials_raw(); +    my $entry = find { $mntpoint eq $_->{mntpoint} } @entries; +    die "mountpoint not found" if !$entry; +    my %h; +    foreach (@$mount_opt) { +        my @var = split(/=/); +        $h{$var[0]} = $var[1]; +    } +    foreach my $key (qw(username password)) { +        $entry->{$key} = $h{$key}; +    } +    save_credentials(\@entries); +} + + +sub read_credentials_raw() { +    from_double_quoted(cat_(secrets_file())); +} + +sub read_credentials { +    my ($mntpoint) = @_; +    find { $mntpoint eq $_->{mntpoint} } read_credentials_raw(); +} + +# Comments are indicated by a '#' character and the rest of the line +# is ignored. Empty lines are ignored too. +# +# Each line consists of two or three items separated by spaces or tabs. +# If an item contains one of the characters space, tab, #, \ or ", this +# character must be escaped by a preceding \. Alternatively, the item +# may be enclosed in double quotes. + +sub from_double_quoted { +    my ($file) = @_; +    my @l; +    my @lines = split("\n",$file); +    foreach (@lines) { +	my ($mnt, $user, $pass, $comment);  +	if (/^\s*(#.*)?$/) { +	    $comment = $1; +	} else { +            if (/^(?:"((?:\\.|[^"])*)"|((?:\\.|[^"\s#])+))\s+(?:"((?:\\.|[^"])*)"|((?:\\.|[^"\s#])+))(?:\s+(?:"((?:\\.|[^"])*)"|((?:\\.|[^"\s#])+)))?(?:\s*|\s*(#.*))?$/) { +	            $mnt = "$1$2"; +		    $mnt =~ s/\\(.)/$1/g; +		    $user = "$3$4"; +	            $user =~ s/\\(.)/$1/g; +		    $pass = "$5$6"; +	            $pass =~ s/\\(.)/$1/g; +		    $comment=$7; +	    } else { +		    die "bad entry $_"; +	    } +        } +        push @l, { 'mntpoint' => $mnt, 'username' => $user, 'password' => $pass, 'comment' => $comment }; +    } +    @l; +} + +sub to_double_quoted { +    my (@l) = @_; +    join(' ', map { +	s/(["\\])/\\$1/g; +	/\s/ ? qq("$_") : $_; +    } @l); +} + +1; diff --git a/perl-install/fs/remote/nfs.pm b/perl-install/fs/remote/nfs.pm new file mode 100644 index 000000000..f7a98cb69 --- /dev/null +++ b/perl-install/fs/remote/nfs.pm @@ -0,0 +1,74 @@ +package fs::remote::nfs; + +use strict; +use diagnostics; + +use common; +use fs::remote; +use network::tools; +use log; + +our @ISA = 'fs::remote'; + +sub to_fstab_entry { +    my ($class, $e) = @_; +    $class->to_fstab_entry_raw($e, 'nfs'); +} +sub comment_to_string { +    my ($_class, $comment) = @_; +    member($comment, qw(* 0.0.0.0/0.0.0.0 (everyone))) ? '' : $comment; +} +sub from_dev {  +    my ($_class, $dev) = @_; +    $dev =~ m|(.*?):(.*)|; +} +sub to_dev_raw { +    my ($_class, $server, $name) = @_; +    $server . ':' . $name; +} + +sub check { +    my ($_class, $in) = @_; +    $in->do_pkgs->ensure_files_are_installed([ [ qw(nfs-utils showmount) ] , [ qw(nmap nmap) ] ]); +    require services; +    services::start_not_running_service('rpcbind'); +    services::start('nfs-common'); #- TODO: once nfs-common is fixed, it could use start_not_running_service() +    1; +} + +sub find_servers { +    my @hosts; +    my %servers; +    my @routes = cat_("/proc/net/route"); +    @routes = reverse(@routes) if common::cmp_kernel_versions(c::kernel_version(), "2.6.39") >= 0; +    foreach (@routes) { +	if (/^(\S+)\s+([0-9A-F]+)\s+([0-9A-F]+)\s+[0-9A-F]+\s+\d+\s+\d+\s+(\d+)\s+([0-9A-F]+)/) { +	    my $net = network::tools::host_hex_to_dotted($2); +	    my $gateway = $3; +	    # get the netmask in binary and remove leading zeros +	    my $mask = unpack('B*', pack('h*', $5)); +	    $mask =~ s/^0*//; +	    push @hosts, $net . "/" . length($mask) if $gateway eq '00000000' && $net ne '169.254.0.0'; +	} +     } +    # runs the nmap command on the local subnet +    my $cmd = "/usr/bin/nmap -p 111 --open --system-dns -oG - " . (join ' ',@hosts); +    open my $FH, "$cmd |" or die "Could not perform nmap scan - $!"; +    foreach (<$FH>) {  +      my ($ip, $name) = /^H\S+\s(\S+)\s+\((\S*)\).+Port/ or next; +      $servers{$ip} ||= { ip => $ip, name => $name || $ip }; +    } +    close $FH; +    values %servers; +} + +sub find_exports { +    my ($_class, $server) = @_; + +    my @l; +    run_program::raw({ timeout => 1 }, "showmount", '>', \@l, "--no-headers", "-e", $server->{ip} || $server->{name}); + +    map { if_(/(\S+(\s*\S+)*)\s+(\S+)/, { name => $1, comment => $3, server => $server }) } @l; +} + +1; diff --git a/perl-install/fs/remote/smb.pm b/perl-install/fs/remote/smb.pm new file mode 100644 index 000000000..d440fc174 --- /dev/null +++ b/perl-install/fs/remote/smb.pm @@ -0,0 +1,218 @@ +package fs::remote::smb; + +use strict; +use diagnostics; + +use common; +use fs::mount_options; +use fs::remote; + + +our @ISA = 'fs::remote'; + +sub to_fstab_entry { +    my ($class, $e) = @_; +    my $part = $class->to_fstab_entry_raw($e, 'cifs'); +    if ($e->{server}{username}) { +	my ($options, $unknown) = fs::mount_options::unpack($part); +	$options->{"$_="} = $e->{server}{$_} foreach qw(username password domain); +	fs::mount_options::pack($part, $options, $unknown); +    } +    $part; +} +sub from_dev {  +    my ($_class, $dev) = @_; +    $dev =~ m|//(.*?)/(.*)|; +} +sub to_dev_raw { +    my ($_class, $server, $name) = @_; +    '//' . $server . '/' . $name; +} + +sub check { +    my ($_class, $in) = @_; +    $in->do_pkgs->ensure_binary_is_installed('samba-client', 'nmblookup'); +} + +sub smbclient { +    my ($server) = @_; +    my $name  = $server->{name} || $server->{ip}; +    my $ip    = $server->{ip} ? "-I $server->{ip}" : ''; +    my $group = $server->{group} ? qq( -W "$server->{group}") : ''; + +    my $U = $server->{username} ? sprintf("%s/%s%%%s", @$server{'domain', 'username', 'password'}) : '%'; +    my %h; +    foreach (`smbclient -g -U "$U" -L "$name" $ip$group 2>/dev/null`) { +	if (my ($type, $v1, $v2) = /(.*)\|(.*)\|(.*)/) { +	    push @{$h{$type}}, [ $v1, $v2 ]; +	} elsif (/^Error returning browse list/) { +	    push @{$h{Error}}, $_; +	} +    } +    \%h; +} + +sub find_servers { +    my (undef, @l) = `nmblookup "*"; nmblookup -M -- -`; +    s/\s.*\n// foreach @l; +    require network::network; +    my @servers = grep { network::network::is_ip($_) } @l; +    return unless @servers; +    my %servers; +    $servers{$_}{ip} = $_ foreach @servers; +    my ($ip, $browse); +    foreach (`nmblookup -A @servers`) { +	my $nb = /^Looking up status of (\S+)/ .. /^$/ or next; +	if ($nb == 1) { +	    $ip = $1; +	} elsif (/<00>/) { +	    $servers{$ip}{/<GROUP>/ ? 'group' : 'name'} ||= lc first(/(\S+)/); +	} elsif (/__MSBROWSE__/) { +	    $browse ||= $servers{$ip}; +	} +    } +    if ($browse) { +	my %l; +	my $workgroups = smbclient($browse)->{Workgroup} || []; +	foreach (@$workgroups) { +	    my ($group, $name) = map { lc($_) } @$_; + +	    # already done +	    next if any { $group eq $_->{group} } values %servers; + +	    $l{$name} = $group; +	} +	if (my @l = keys %l) { +	    foreach (`nmblookup @l`) { +		$servers{$1} = { name => $2, group => $l{$2} } if /(\S+)\s+([^<]+)<00>/; +	    } +	} +    } +    values %servers; +} + +sub find_exports { +    my ($_class, $server) = @_; +    my @l; + +    my $browse = smbclient($server); +    if (my $err = find { /NT_STATUS_/ } @{$browse->{Error} || []}) { +	die $err; +    } +    foreach (@{$browse->{Disk} || []}) { +	my ($name, $comment) = @$_; +	push @l, { name => $name, type => 'Disk', comment => $comment, server => $server } +	  if $name !~ /\$$/ && $name !~ /netlogon|NETLOGON|SYSVOL/; +    } +    @l; +} + +sub authentications_available { +    my ($server) = @_; +    map { if_(/^auth.\Q$server->{name}.\E(.*)/, $1) } all("/etc/samba"); +} + +sub to_credentials { +    my ($server_name, $username) = @_; +    $username or die 'to_credentials'; +    "/etc/samba/auth.$server_name.$username"; +} + +sub fstab_entry_to_credentials { +    my ($part) = @_;     + +    my ($server_name) = fs::remote::smb->from_dev($part->{device}) or return; + +    my ($options, $unknown) = fs::mount_options::unpack($part); +    $options->{'username='} && $options->{'password='} or return; +    my %h = map { $_ => delete $options->{"$_="} } qw(username password); +    $h{file} = $options->{'credentials='} = to_credentials($server_name, $h{username}); +    fs::mount_options::pack_($part, $options, $unknown), \%h; +} + +sub remove_bad_credentials { +    my ($server) = @_; +    unlink to_credentials($server->{name}, $server->{username}); +} + +sub save_credentials { +    my ($credentials) = @_; +    my $file = $credentials->{file}; +    output_with_perm("$::prefix$file", 0640, map { "$_=$credentials->{$_}\n" } qw(username password)); +} + + +sub read_credentials_raw { +    my ($file) = @_; +    my %h = map { /(.*?)\s*=\s*(.*)/ } cat_("$::prefix$file"); +    \%h; +} + +sub read_credentials { +    my ($server, $username) = @_; +    put_in_hash($server, read_credentials_raw(to_credentials($server->{name}, $username))); +} + + +sub write_smb_conf { +    my ($domain) = @_; + +    #- was going to just have a canned config in samba-winbind +    #- and replace the domain, but sylvestre/buchan did not bless it yet + +    my $f = "$::prefix/etc/samba/smb.conf"; +    rename $f, "$f.orig"; +    output($f, " +[global] +	workgroup = $domain   +	server string = Samba Server %v +	security = domain   +	encrypt passwords = Yes +	password server = * +	log file = /var/log/samba/log.%m +	max log size = 50 +	socket options = TCP_NODELAY SO_RCVBUF=8192 SO_SNDBUF=8192 +	unix charset = ISO8859-15 +	os level = 18 +	local master = No +	dns proxy = No +	idmap uid = 10000-20000 +	idmap gid = 10000-20000 +	winbind separator = + +	template homedir = /home/%D/%U +	template shell = /bin/bash +	winbind use default domain = yes +"); +} + +sub write_smb_ads_conf { +    my ($domain, $realm) = @_; + +    #- was going to just have a canned config in samba-winbind +    #- and replace the domain, but sylvestre/buchan did not bless it yet + +    my $f = "$::prefix/etc/samba/smb.conf"; +    rename $f, "$f.orig"; +    output($f, " +[global] +        workgroup = $domain +        realm  = $realm +        server string = Samba Member %v +        security = ads +        encrypt passwords = Yes +        password server = * +        log file = /var/log/samba/log.%m +        max log size = 50 +        socket options = TCP_NODELAY SO_RCVBUF=8192 SO_SNDBUF=8192 +        os level = 18 +        local master = No +        dns proxy = No +        winbind uid = 10000-20000 +        winbind gid = 10000-20000 +        winbind separator = + +        template homedir = /home/%D/%U +        template shell = /bin/bash +        winbind use default domain = yes +"); +} +1; diff --git a/perl-install/fs/type.pm b/perl-install/fs/type.pm new file mode 100644 index 000000000..141d5b5e2 --- /dev/null +++ b/perl-install/fs/type.pm @@ -0,0 +1,456 @@ +package fs::type; + +use diagnostics; +use strict; + +use common; +use devices; + +=head1 SYNOPSYS + +B<fs::type> enables to perform various tests on filesystem types. + +=head1 Functions + +=over + +=cut + +our @ISA = qw(Exporter); +our @EXPORT = qw( +   isBlockCheckable isEmpty isExtended isFormatable isTrueLocalFS isTrueFS isDos isSwap isOtherAvailableFS isRawLVM isRawRAID isRawLUKS isRAID isLVM isLUKS isMountableRW isNonMountable isPartOfLVM isPartOfRAID isPartOfLoopback isLoopback isMounted isBusy isSpecial isApple isAppleBootstrap isBIOS_GRUB isESP isFat_or_NTFS isnormal_Fat_or_NTFS isRecovery +   maybeFormatted set_isFormatted defaultFS +); + + +my (%type_name2pt_type, %type_name2fs_type, %fs_type2pt_type, %pt_type2fs_type, %type_names); + +{ +    my @list_types = ( +	important => [ +  0x82 => 'swap',     'Linux swap', +  0x83 => 'ext2',     'Linux native', +  0x83 => 'ext3',     'Journalised FS: ext3', +  0x83 => 'ext4',     'Journalised FS: ext4', +  0x83 => 'btrfs',    'Journalised FS: Btrfs', +(is_uefi() ? +  (0xef => 'vfat',     'EFI System Partition') : +  ('BIOS_GRUB' => 'BIOS_GRUB',  'BIOS boot or Empty partition') +), +if_(arch() =~ /i.86|x86_64/, +  0x83 => 'xfs',      'Journalised FS: XFS', +  0x83 => 'jfs',      'Journalised FS: JFS', +  0x0b => 'vfat',     'FAT32', +  0x07 => 'ntfs-3g',  'NTFS-3G', +  0x07 => 'ntfs',     'NTFS', +  0x07 => 'ntfs3',    'NTFS3', +  0x07 => 'exfat',    'exFAT', +), +	], + +        non_fs_type => [ +  0x83 => '',         'Encrypted', +  0x8e => '',         'Linux Logical Volume Manager', +  0xfd => '',         'Linux RAID', +	], + +	special => [ +  0x0  => '',         'Empty', +  0x05 => '',         'Extended', +  0x0f => '',         'W95 Extended (LBA)', +  0x85 => '',         'Linux extended', +	], + +	other => [ + if_(arch() =~ /^i.86|x86_64/, +  0x01 => 'vfat',     'FAT12', +  0x02 => '',         'XENIX root', +  0x03 => '',         'XENIX usr', +  0x04 => 'vfat',     'FAT16 <32M', +  0x06 => 'vfat',     'FAT16', +  0x07 => 'hpfs',     'HPFS', +  0x08 => '',         'AIX', +), +  0x09 => '',         'AIX bootable', +  0x0a => '',         'OS/2 Boot Manager', +  0x0c => 'vfat',     'W95 FAT32 (LBA)', +  0x0e => 'vfat',     'W95 FAT16 (LBA)', +  0x10 => '',         'OPUS', +  0x11 => '',         'Hidden FAT12', +  0x12 => '',         'Compaq diagnostics', +  0x14 => '',         'Hidden FAT16 <32M', +  0x16 => '',         'Hidden FAT16', +  0x17 => 'ntfs',     'Hidden HPFS/NTFS', +  0x18 => '',         'AST SmartSleep', +  0x1b => 'vfat',     'Hidden W95 FAT32',       # \  +  0x1c => 'vfat',     'Hidden W95 FAT32 (LBA)', #  > don't change label, it's used to know if it's not a boot partition in bootloader.pm +  0x1e => 'vfat',     'Hidden W95 FAT16 (LBA)', # / +  0x24 => '',         'NEC DOS', +  0x39 => '',         'Plan 9', +  0x3c => '',         'PartitionMagic recovery', +  0x40 => '',         'Venix 80286', +  0x41 => '',         'PPC PReP Boot', +  0x42 => '',         'SFS', +  0x4d => '',         'QNX4.x', +  0x4e => '',         'QNX4.x 2nd part', +  0x4f => '',         'QNX4.x 3rd part', +  0x50 => '',         'OnTrack DM', +  0x51 => '',         'OnTrack DM6 Aux1', +  0x52 => '',         'CP/M', +  0x53 => '',         'OnTrack DM6 Aux3', +  0x54 => '',         'OnTrackDM6', +  0x55 => '',         'EZ-Drive', +  0x56 => '',         'Golden Bow', +  0x5c => '',         'Priam Edisk', +  0x61 => '',         'SpeedStor', +  0x63 => '',         'GNU HURD or SysV', +  0x64 => '',         'Novell Netware 286', +  0x65 => '',         'Novell Netware 386', +  0x70 => '',         'DiskSecure Multi-Boot', +  0x75 => '',         'PC/IX', +  0x80 => '',         'Old Minix', +  0x81 => '',         'Minix / old Linux', +  0x83 => 'f2fs',     'Journalised FS: F2FS', +  0x83 => 'reiserfs', 'Journalised FS: ReiserFS', +  0x83 => 'nilfs2',   'Journalised FS: NILFS2', +  0x84 => '',         'OS/2 hidden C: drive', +  0x86 => '',         'NTFS volume set (0x86)', +  0x87 => '',         'NTFS volume set (0x87)', +  0x93 => '',         'Amoeba', +  0x94 => '',         'Amoeba BBT', +  0x9f => '',         'BSD/OS', +  0xa0 => '',         'IBM Thinkpad hibernation', +  0xa5 => '',         'FreeBSD', +  0xa6 => '',         'OpenBSD', +  0xa7 => '',         'NeXTSTEP', +  0xa8 => '',         'Darwin UFS', +  0xa9 => '',         'NetBSD', +  0xab => '',         'Darwin boot', +  0xb7 => '',         'BSDI fs', +  0xb8 => '',         'BSDI swap', +  0xbb => '',         'Boot Wizard hidden', +  0xbe => '',         'Solaris boot', +  0xbf => '',         'Microsoft XBox OS Partitions', +  0xc1 => '',         'DRDOS/sec (FAT-12)', +  0xc4 => '',         'DRDOS/sec (FAT-16 < 32M)', +  0xc6 => '',         'DRDOS/sec (FAT-16)', +  0xc7 => '',         'Syrinx', +  0xda => '',         'Non-FS data', +  0xdb => '',         'CP/M / CTOS / ...', +  0xde => '',         'Dell Utility', +  0xdf => '',         'BootIt', +  0xe1 => '',         'SpeedStor (FAT-12)', +  0xe3 => '',         'DOS R/O', +  0xe4 => '',         'SpeedStor (FAT-16)', +  0xeb => 'befs',     'BeOS fs', +  0xee => '',         'EFI GPT', +  0xf0 => '',         'Linux/PA-RISC boot', +  0xf4 => '',         'SpeedStor (large part.)', +  0xf2 => '',         'DOS secondary', +  0xfe => '',         'LANstep', +  0xff => '',         'BBT', +	], +    ); + +    foreach (group_by2(@list_types)) { +	my ($name, $l) = @$_; +	for (my $i = 0; defined $l->[$i]; $i += 3) { +	    my $pt_type   = $l->[$i]; +	    my $fs_type   = $l->[$i + 1]; +	    my $type_name = $l->[$i + 2]; +	    !exists $type_name2fs_type{$type_name} or internal_error("'$type_name' is not unique"); +	    $type_name2fs_type{$type_name} = $fs_type; +	    $type_name2pt_type{$type_name} = $pt_type; + +	    $fs_type2pt_type{$fs_type} ||= $pt_type; +	    $pt_type2fs_type{$pt_type} ||= $fs_type; +	    push @{$type_names{$name}}, $type_name; +	} +    } +} + + +sub type_names {  +    my ($expert, $o_hd) = @_; +    my @l = @{$type_names{important}}; +    push @l, grep { $_ ne 'Encrypted' } @{$type_names{non_fs_type}}; +    push @l, sort @{$type_names{other}} if $expert; +    # not show partition types which have no associated filesystem for LVM LV: +    if ($o_hd && isLVM($o_hd)) { +	@l = grep { $type_name2fs_type{$_} } @l; +	@l = uniq_ { $type_name2fs_type{$_} } @l; +	(@l, @{$type_names{non_fs_type}}); +    } else { +	@l; +    } +} + +sub type_name2subpart { +    my ($name) = @_; +    exists $type_name2fs_type{$name} &&  +      { type_name => $name, +	fs_type => $type_name2fs_type{$name}, pt_type => $type_name2pt_type{$name} }; +} + +sub part2type_name {  +    my ($part) = @_; +    my @names = keys %type_name2fs_type; +    +    my $pt_type = defined $part->{pt_type} ? $part->{pt_type} : $part->{fs_type} && $fs_type2pt_type{$part->{fs_type}}; +    if (defined $pt_type) { +	@names = grep { $pt_type eq $type_name2pt_type{$_} } @names; +    } +    if (my $fs_type = $part->{fs_type} || $part->{pt_type} && $pt_type2fs_type{$part->{pt_type}}) { +	@names = grep { $fs_type eq $type_name2fs_type{$_} } @names; +    } +    if (@names > 1) { +	log::l("ERROR: (part2type_name) multiple match for $part->{pt_type} $part->{fs_type}"); +    } +    first(@names); +} +sub type_name2pt_type {  +    local ($_) = @_; +    /0x(.*)/ ? hex $1 : $type_name2pt_type{$_} || $_; +} + + +sub pt_type2subpart { +    my ($pt_type) = @_; +    my $fs_type = $pt_type2fs_type{$pt_type}; +    { pt_type => $pt_type, if_($fs_type, fs_type => $fs_type) }; +} +sub fs_type2subpart { +    my ($fs_type) = @_; +    my $pt_type = $fs_type2pt_type{$fs_type}; +    { fs_type => $fs_type, if_($pt_type, pt_type => $pt_type) }; +} +sub set_fs_type { +    my ($part, $fs_type) = @_; +    put_in_hash($part, fs_type2subpart($fs_type)); +} +sub set_pt_type { +    my ($part, $pt_type) = @_; +    put_in_hash($part, pt_type2subpart($pt_type)); +} +sub suggest_fs_type { +    my ($part, $fs_type) = @_; +    set_fs_type($part, $fs_type) if !$part->{pt_type} && !$part->{fs_type}; +} +sub set_type_subpart { +    my ($part, $subpart) = @_; +    if (exists $subpart->{pt_type} && exists $subpart->{fs_type}) { +	$part->{fs_type} = $subpart->{fs_type}; +	$part->{pt_type} = $subpart->{pt_type}; +    } elsif (exists $subpart->{pt_type}) { +	set_pt_type($part, $subpart->{pt_type}); +    } elsif (exists $subpart->{fs_type}) { +	set_fs_type($part, $subpart->{fs_type}); +    } else { +	log::l("ERROR: (set_type_subpart) subpart has no type"); +    } +} + +sub fs_type_from_magic { +    my ($part) = @_; +    if (exists $part->{fs_type_from_magic}) { +	$part->{fs_type_from_magic}; +    } else { +	my $type = type_subpart_from_magic($part); +	$type && $type->{fs_type}; +    } +} + +sub call_blkid { +    my ($part) = @_; + +    # IMPORTANT: Always use the -p argument with blkid. See r7324 commit msg +    my %h = map { +	if_(/(.*?)=(.*)/, $1 => $2); +    } run_program::get_stdout_raw({ timeout => 30 }, 'blkid', '2>', '/dev/null', '-o', 'udev', '-p', devices::make($part->{device})); + +    \%h; +} + +sub type_subpart_from_magic {  +    my ($part) = @_; +    my $ids = call_blkid($part); + +    my $p; +    if ($ids->{ID_FS_USAGE} eq 'raid') { +	my $name = { +	    linux_raid_member => "Linux RAID", +	    LVM1_member => 'Linux Logical Volume Manager', +	    LVM2_member => 'Linux Logical Volume Manager', +	}->{$ids->{ID_FS_TYPE}}; + +	$p = type_name2subpart($name) if $name; +    } elsif ($ids->{ID_FS_USAGE} eq 'crypto') { +	$p = type_name2subpart('Encrypted'); +    } elsif (my $fs_type = $ids->{ID_FS_TYPE}) { +	$fs_type = 'ntfs-3g' if $fs_type eq 'ntfs'; +	$p = fs_type2subpart($fs_type) or log::l("unknown filesystem $fs_type returned by blkid"); +    } + +    if ($p) { +	$p->{fs_type} = '' if $part->{pt_type} eq 'BIOS_GRUB' && $p->{fs_type} ne 'iso9660'; +	$part->{fs_type_from_magic} = $p->{fs_type}; +	$p->{device_LABEL} = $ids->{ID_FS_LABEL} if $ids->{ID_FS_LABEL}; +	$p->{device_UUID} = $ids->{ID_FS_UUID} if $ids->{ID_FS_UUID}; +	log::l("blkid gave: $p->{fs_type} $p->{device_UUID} $p->{device_LABEL}"); +    } +    $p; +} + +# helpers +sub defaultFS() { 'ext4' } +sub true_local_fs_types() { qw(btrfs ext3 ext2 ext4 f2fs reiserfs xfs jfs) } + +sub isEmpty { !$_[0]{fs_type} && !$_[0]{pt_type} } +sub isBIOS_GRUB { $_[0]{pt_type} eq 'BIOS_GRUB' } +sub isESP { $_[0]{pt_type} == 0xef && member($_[0]{fs_type}, qw(fat32 vfat)) } +sub isExtended { $_[0]{pt_type} == 5 || $_[0]{pt_type} == 0xf || $_[0]{pt_type} == 0x85 } +sub isBlockCheckable { !member($_[0]{fs_type}, qw(btrfs hfs ntfs ntfs-3g reiserfs xfs)) } +sub isRawLVM { $_[0]{pt_type} == 0x8e || $_[0]{type_name} eq 'Linux Logical Volume Manager' } +sub isRawRAID { $_[0]{pt_type} == 0xfd || $_[0]{type_name} eq 'Linux RAID' } +sub isRawLUKS { $_[0]{type_name} eq 'Encrypted' } +sub isSwap { $_[0]{fs_type} eq 'swap' } +sub isDos { ${{ 1 => 1, 4 => 1, 6 => 1 }}{$_[0]{pt_type}} } +sub isFat_or_NTFS { member($_[0]{fs_type}, qw(vfat ntfs ntfs3 ntfs-3g)) } +sub isnormal_Fat_or_NTFS { grep { isFat_or_NTFS($_) && !isESP($_) && !isRecovery($_) } @_ } +sub isApple { $_[0]{pt_type} == 0x401 && defined $_[0]{isDriver} } +sub isAppleBootstrap { $_[0]{pt_type} == 0x401 && defined $_[0]{isBoot} } +sub isRecovery {  +    isFat_or_NTFS($_[0]) && ($_[0]{type_name} =~ /^Hidden/ || +      $_[0]{pt_type} == 0x12 || # "Compaq diagnostics" +        member($_[0]{device_LABEL} ,  +            # Extracted from /usr/lib/udev/rules.d/80-udisks2.rules +            # Hopefuly we'll ask to udev/udisk2 someday +            # generated by grep Recovery /usr/lib/udev/rules.d/80-udisks2.rules : +            qw(Recovery RECOVERY Lenovo_Recovery HP_RECOVERY Recovery_Partition DellUtility DellRestore IBM_SERVICE SERVICEV001 SERVICEV002 SYSTEM_RESERVED System_Reserved WINRE_DRV DIAGS IntelRST), +            # gathered over the years (Hald, mga#1371, mga#15999): +            qw(PQSERVICE Packard_Bell Push_Button_Reset SYSTEM_DRV)) +    ); +} + +=item isTrueLocalFS($part) + +Like isTrueFS(), to make a distinction between ext3/reiserfs/... and NFS + => allow /home on NFS + +=cut + +sub isTrueFS { isTrueLocalFS($_[0]) || $_[0]{fs_type} eq 'nfs' } + +=item isTrueFS($part) + +Is is a general purpose file system with the right Unix properties + +=cut + +sub isTrueLocalFS { member($_[0]{fs_type}, true_local_fs_types()) } + +=item isOtherAvailableFS($part) + +Is it another OS that linux can access its filesystem + +=cut + +sub isOtherAvailableFS { isESP($_[0]) || isFat_or_NTFS($_[0]) || member($_[0]{fs_type}, 'ufs', 'hfs', 'iso9660', 'nilfs2', 'exfat') } +sub isMountableRW { (isTrueFS($_[0]) || isOtherAvailableFS($_[0])) && $_[0]{fs_type} ne 'ntfs' } +sub cannotBeMountable {  +    my ($part) = @_; +    isRawRAID($part) || isRawLUKS($part) || isRawLVM($part) || isBIOS_GRUB($part); +} + +=item isFormatable($part) + +Is not a special sg that cannot be mounted/formatted (parts of RAID/LVM, BIOS_GRUB). Basically the reverse of cannotBeMountable(). + +=cut + +sub isFormatable { +    my ($part) = @_; +    !cannotBeMountable($part); +} + +sub isNonMountable {  +    my ($part) = @_; +    cannotBeMountable($part) || $part->{fs_type} eq 'ntfs' && !$part->{isFormatted} && $part->{notFormatted}; +} + +sub isPartOfLVM { defined $_[0]{lvm} } +sub isPartOfRAID { defined $_[0]{raid} } +sub isPartOfLoopback { defined $_[0]{loopback} } +sub isRAID { $_[0]{device} =~ /^md/ && defined $_[0]{level} } +sub isUBD { $_[0]{device} =~ /^ubd/ } #- should be always true during an $::uml_install +sub isLVM { $_[0]{VG_name} || $_[0]{lv_name} } +sub isLUKS { defined $_[0]{dmcrypt_name} } +sub isLoopback { defined $_[0]{loopback_file} } +sub isMounted { $_[0]{isMounted} } +sub isBusy { isMounted($_[0]) || isPartOfRAID($_[0]) || isPartOfLVM($_[0]) || $_[0]{dm_active} || isPartOfLoopback($_[0]) } +sub isSpecial { isRAID($_[0]) || isLVM($_[0]) || isLoopback($_[0]) || isUBD($_[0]) } + +=item is_dmraid($hd) + +Check that a disk (not a partition) is in a fake/soft RAID + +=cut + +sub is_dmraid { $_[0]{bus} =~ /^dmraid_/ } + +sub can_be_this_fs_type { +    my ($part, $fs_type) = @_; +    can_be_one_of_those_fs_types($part, $fs_type); +} +sub can_be_one_of_those_fs_types { +    my ($part, @fs_types) = @_; +    $part->{fs_type} or return; +    $part->{fs_type} eq 'auto' || listlength(intersection(\@fs_types, [ split(':', $part->{fs_type}) ])); +} + +sub maybeFormatted {  +    my ($part) = @_; +    $part->{isFormatted} || !$part->{notFormatted} && (!$part->{bad_fs_type_magic} || $part->{options} =~ /encrypted/); +} +sub set_isFormatted { +    my ($part, $val) = @_; +    $part->{isFormatted} = $val; +    $part->{notFormatted} = !$val; +    delete $part->{bad_fs_type_magic}; +    delete $part->{fs_type_from_magic}; +} + +=item check($fs_type, $_hd, $part) + +Called before before modifying $part->{fs_type} + +=cut + +sub check { +    my ($fs_type, $_hd, $part) = @_; +    $fs_type eq "jfs" && $part->{size} < MB(16) and die N("You cannot use JFS for partitions smaller than 16MB"); +    $fs_type eq "reiserfs" && $part->{size} < MB(32) and die N("You cannot use ReiserFS for partitions smaller than 32MB"); +    $fs_type eq "btrfs" && $part->{size} < MB(256) and die N("You cannot use btrfs for partitions smaller than 256MB"); +} + +sub guessed_by_mount() { +    grep { $_ && !/nodev/ } chomp_(cat_('/etc/filesystems')); +} + +sub directories_needed_to_boot_not_ESP() { +    qw(/ /usr /var /boot /tmp); +} +sub directories_needed_to_boot() {  +    directories_needed_to_boot_not_ESP(), '/boot/EFI'; +} + +sub carry_root_loopback { +    my ($part) = @_; +    any { $_->{mntpoint} eq '/' } @{$part->{loopback} || []}; +} + +=back + +=cut + +1; diff --git a/perl-install/fs/wild_device.pm b/perl-install/fs/wild_device.pm new file mode 100644 index 000000000..ff5a32253 --- /dev/null +++ b/perl-install/fs/wild_device.pm @@ -0,0 +1,115 @@ +package fs::wild_device; + +use diagnostics; +use strict; +use devices; +use common; + + +sub analyze { +    my ($dev) = @_; + +    if ($dev =~ m!^/u?dev/(.*)!) { +	'dev', $dev; +    } elsif ($dev !~ m!^/! && (-e "/dev/$dev" || -e "/dev/$dev")) { +	'dev', "/dev/$dev"; +    } elsif ($dev =~ /^LABEL=(.*)/) { +	'label', $1; +    } elsif ($dev =~ /^UUID=(.*)/) { +	'uuid', $1; +    } elsif (member($dev, qw(none rootfs))) { +	'virtual'; +    } elsif ($dev =~ m!^(\S+):/(\w|$)!) { +	'nfs'; +    } elsif ($dev =~ m!^//\w!) { +	'smb'; +    } elsif ($dev =~ m!^https?://!) { +	'dav'; +    } +} + +sub to_subpart { +    my ($dev) = @_; + +    my $part = { device => $dev, faked_device => 1 }; #- default + +    if (my ($kind, $val) = analyze($dev)) { +	if ($kind eq 'label') {	     +	    $part->{device_LABEL} = $val; +	} elsif ($kind eq 'uuid') {	     +	    $part->{device_UUID} = $val; +	} elsif ($kind eq 'dev') { +	    my %part = (faked_device => 0); +	    if (my $rdev = (stat "$dev")[6]) { +		($part{major}, $part{minor}) = unmakedev($rdev); +	    } + +	    my $symlink = $dev !~ m!mapper/! ? readlink("$dev") : undef; +	    $dev =~ s!/u?dev/!!; + +	    if ($symlink && $symlink !~ m!^/!) { +		my $keep = 1; +		if ($symlink =~ m!/! || $dev =~ m!/!) { +		    $symlink = MDK::Common::File::concat_symlink("/dev/" . dirname($dev), $symlink); +		    $symlink =~ s!^/dev/!! or $keep = 0; +		} +		if ($keep) { +		    $part{device_LABEL} = $1 if $dev =~  m!^disk/by-label/(.*)!; +		    $part{device_UUID} = $1 if $dev =~  m!^disk/by-uuid/(.*)!; +		    $part{device_alias} = $dev; +		    $dev = $symlink; +		} +	    } +	    if (my $part_number = devices::part_number(\%part)) { +		$part{part_number} = $part_number; +	    } +	    $part{device} = $dev; +	    return \%part; +	} +    } else { +	if ($dev =~ m!^/! && -f "$dev") { +	    #- it must be a loopback file or directory to bind +	} else { +	    log::l("part_from_wild_device_name: unknown device $dev"); +	} +    } +    $part; +} + +sub _prefer_device_UUID { +    my ($part) = @_; +    $part->{prefer_device_UUID} ||  +      !$::no_uuid_by_default && devices::should_prefer_UUID($part->{device}); +} + +sub from_part { +    my ($prefix, $part) = @_; + +    if ($part->{prefer_device_LABEL}) { +	'LABEL=' . $part->{device_LABEL}; +    } elsif ($part->{device_alias}) { +	"/dev/$part->{device_alias}"; +    } elsif (!$part->{prefer_device} && $part->{device_UUID} && _prefer_device_UUID($part)) { +	'UUID=' . $part->{device_UUID}; +    } else { +	my $faked_device = exists $part->{faked_device} ?  +	    $part->{faked_device} :  +	    do { +		#- in case $part has been created without using fs::wild_device::to_subpart() +		my ($kind) = analyze($part->{device}); +		$kind ? $kind ne 'dev' : $part->{device} =~ m!^/!; +	    }; +	if ($faked_device) { +	    $part->{device}; +	} elsif ($part->{device} =~ m!^/dev/!) { +	    log::l("ERROR: i have a full device $part->{device}, this should not happen. use fs::wild_device::to_subpart() instead of creating bad part data-structures!"); +	    $part->{device}; +	} else { +	    my $dev = "/dev/$part->{device}"; +	    eval { devices::make("$prefix$dev") }; +	    $dev; +	} +    } +} + +1; | 
