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 | 380 | ||||
-rw-r--r-- | perl-install/fs/get.pm | 86 | ||||
-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 | 112 | ||||
-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 | 306 | ||||
-rw-r--r-- | perl-install/fs/wild_device.pm | 115 |
18 files changed, 3227 insertions, 221 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 index 1b7b977cd..1fa90c29e 100644 --- a/perl-install/fs/format.pm +++ b/perl-install/fs/format.pm @@ -1,25 +1,135 @@ -package fs::format; # $Id$ +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', 'mke2fs', '-F' ], - ext3 => [ 'e2fsprogs', 'mke2fs', '-F', '-j' ], - reiserfs => [ 'reiserfsprogs', 'mkreiserfs', '-ff' ], + 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 => [ 'jfsprogs', 'mkfs.jfs', '-f' ], + jfs => [ 'jfsutils', 'mkfs.jfs', '-f' ], hfs => [ 'hfsutils', 'hformat' ], - dos => [ 'dosfstools', 'mkdosfs' ], - vfat => [ 'dosfstools', 'mkdosfs', '-F', '32' ], + 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; @@ -31,36 +141,117 @@ sub known_type { to_bool($cmds{$part->{fs_type}}); } -sub check_package_is_installed { +sub check_package_is_installed_format { my ($do_pkgs, $fs_type) = @_; my ($pkg, $binary) = @{$cmds{$fs_type} || return}; - $do_pkgs->ensure_binary_is_installed($pkg, $binary); + 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 ($raids, $part, $prefix, $wait_message) = @_; + 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($raids, $part); + raid::format_part($all_hds->{raids}, $part); } elsif (isLoopback($part)) { $wait_message->(N("Creating and formatting file %s", $part->{loopback_file})) if $wait_message; - loopback::format_part($part, $prefix); + 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}) { - require fs; - fs::set_loop($part); + fs::mount::set_loop($part); } my $dev = $part->{real_device} || $part->{device}; @@ -70,7 +261,7 @@ sub part_raw { my $fs_type = $part->{fs_type}; - if ($fs_type eq 'ext2' || $fs_type eq 'ext3') { + if (member($fs_type, qw(ext2 ext3 ext4))) { push @options, "-m", "0" if $part->{mntpoint} =~ m|^/home|; } elsif (isDos($part)) { $fs_type = 'dos'; @@ -80,27 +271,76 @@ sub part_raw { 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 eq 'mke2fs' && $wait_message) { - mke2fs($wait_message, @args) or die N("%s formatting of %s failed", $fs_type, $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 => 60 * 60 }, @args) or die N("%s formatting of %s failed", $fs_type, $dev); + run_program::raw({ timeout => 'never' }, @args) or die N("%s formatting of %s failed", $fs_type, $dev); } - if ($fs_type eq 'ext3') { + 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); } -sub mke2fs { +=item mkfs_ext3($wait_message, @args) + +Display a progression bar whike formating ext3/4 + +=cut + +sub mkfs_ext3 { my ($wait_message, @args) = @_; - open(my $F, "@args |"); + my $cmd = shell_quote_best_effort(@args); + log::l("running: $cmd"); + open(my $F, "$cmd |"); local $/ = "\b"; local $_; @@ -111,41 +351,85 @@ sub mke2fs { 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 wait_message { - my ($in) = @_; - - my ($w, $progress, $last_msg, $displayed); - my $on_expose = sub { $displayed = 1; 0 }; #- declared here to workaround perl limitation - $w, sub { - my ($msg, $current, $total) = @_; - if ($msg) { - $last_msg = $msg; - if (!$w) { - $progress = Gtk2::ProgressBar->new if $in->isa('interactive::gtk'); - $w = $in->wait_message('', [ '', if_($progress, $progress) ]); - if ($progress) { - #- don't show by default, only if we are given progress information - $progress->hide; - $progress->signal_connect(expose_event => $on_expose); - } - } - $w->set($msg); - } elsif ($total) { - if ($progress) { - $progress->set_fraction($current / $total); - $progress->show; - $displayed = 0; - mygtk2::flush() while !$displayed; - } else { - $w->set([ $last_msg, "$current / $total" ]); +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 index 6b982c1e9..00c807738 100644 --- a/perl-install/fs/get.pm +++ b/perl-install/fs/get.pm @@ -1,36 +1,50 @@ -package fs::get; # $Id$ +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 => [], loopbacks => [], raw_hds => [], nfss => [], smbs => [], davs => [], special => [] }; + { 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->{loopbacks}}; + @parts, @{$all_hds->{raids}}, @{$all_hds->{dmcrypts}}, @{$all_hds->{loopbacks}}; } sub really_all_fstab { my ($all_hds) = @_; my @l = fstab($all_hds); - @l, @{$all_hds->{raw_hds}}, @{$all_hds->{nfss}}, @{$all_hds->{smbs}}, @{$all_hds->{davs}}; + @l, (grep { !$_->{is_removable} } @{$all_hds->{raw_hds}}), @{$all_hds->{nfss}}, @{$all_hds->{smbs}}, @{$all_hds->{davs}}; } sub fstab_and_holes { - my ($all_hds) = @_; - hds_fstab_and_holes(hds($all_hds)), @{$all_hds->{raids}}, @{$all_hds->{loopbacks}}; + 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) = @_; - grep { isEmpty($_) } fstab_and_holes($all_hds); + 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(@_); @@ -48,16 +62,27 @@ sub hds { (@{$all_hds->{hds}}, @{$all_hds->{lvms}}); } -#- get all normal partition including special ones as found on sparc. +=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 = $_->{totalsectors} - sum map { $_->{size} } @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 { @@ -69,8 +94,8 @@ sub hds_fstab_and_holes { sub device2part { my ($dev, $fstab) = @_; - my $subpart = fs::subpart_from_wild_device_name($dev); - my $part = find { fsedit::is_same_hd($subpart, $_) } @$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; } @@ -87,7 +112,7 @@ sub file2part { $file = $b_keep_simple_symlinks ? common::expand_symlinks_but_simple("$::prefix$file") : expand_symlinks("$::prefix$file"); unless ($file =~ s/^$::prefix//) { - my $part = find { loopback::carryRootLoopback($_) } @$fstab or die; + my $part = find { fs::type::carry_root_loopback($_) } @$fstab or die; log::l("found $part->{mntpoint}"); $file =~ s|/initrd/loopfs|$part->{mntpoint}|; } @@ -110,6 +135,15 @@ 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); @@ -125,4 +159,30 @@ sub up_mount_point { } } +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 index 57ade611f..0b63f7260 100644 --- a/perl-install/fs/mount_options.pm +++ b/perl-install/fs/mount_options.pm @@ -1,16 +1,18 @@ -package fs::mount_options; # $Id$ +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; @@ -22,33 +24,34 @@ sub unpack { my ($non_defaults, $user_implies) = list(); - my @auto_fs = fs::auto_fs(); + my @auto_fs = fs::type::guessed_by_mount(); my %per_fs = ( iso9660 => [ qw(unhide) ], - vfat => [ qw(umask=0 umask=0022) ], + vfat => [ qw(flush umask=0 umask=0022) ], ntfs => [ qw(umask=0 umask=0022) ], nfs => [ qw(rsize=8192 wsize=8192) ], - smbfs => [ qw(username= password=) ], - davfs => [ qw(username= password= uid= gid=) ], + cifs => [ qw(username= password=) ], + davfs2 => [ qw(username= password= uid= gid=) ], reiserfs => [ 'notail' ], ); - push @{$per_fs{$_}}, 'usrquota', 'grpquota' foreach 'ext2', 'ext3', 'xfs'; + 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) { - $part->{fs_type} eq $fs || $part->{fs_type} eq 'auto' && member($fs, @auto_fs) or next; + member($part->{fs_type}, $fs, 'auto') && member($fs, @auto_fs) or next; $non_defaults->{$_} = 1 foreach @$l; } - $non_defaults->{encrypted} = 1 if !$part->{isFormatted} || isSwap($part); - - $non_defaults->{supermount} = 1 if $part->{fs_type} =~ /:/ || member($part->{fs_type}, 'auto', @auto_fs); + $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 'user') { - $options{$_} = 1 foreach 'user', @$user_implies; + if ($_ eq 'defaults') { + #- skip + } elsif (member($_, 'user', 'users')) { + $options{$_} = 1 foreach $_, @$user_implies; } elsif (exists $non_defaults->{$_}) { $options{$_} = 1; } elsif ($defaults->{$_}) { @@ -83,8 +86,9 @@ sub pack_ { push @l, 'umask=' . min(@umasks); } - if (delete $options->{user}) { - push @l, 'user'; + if (my $user = find { delete $options->{$_} } 'users', 'user') { + push @l, $user; + delete $options->{user}; foreach (@$user_implies) { if (!delete $options->{$_}) { # overriding @@ -99,45 +103,50 @@ sub pack_ { } sub pack { my ($part, $options, $unknown) = @_; - $part->{options} = pack_($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' => '', + '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)."), - 'noatime' => N("Do not update inode access times on this file system + '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 file system to be mounted)."), +the -a option will not cause the filesystem to be mounted)."), - 'nodev' => N("Do not interpret character or block special devices on the file system."), + '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 -file system. This option might be useful for a server that has file systems +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 file system read-only."), + 'ro' => N("Mount the filesystem read-only."), - 'sync' => N("All I/O to the file system should be done synchronously."), + 'sync' => N("All I/O to the filesystem should be done synchronously."), - 'supermount' => '', + 'users' => N("Allow every user to mount and umount the filesystem."), - 'user' => N("Allow an ordinary user to mount the file system. The -name of the mounting user is written to mtab so that he can unmount the file -system again. This option implies the options noexec, nosuid, and nodev -(unless overridden by subsequent options, as in the option line -user,exec,dev,suid )."), + 'user' => N("Allow an ordinary user to mount the filesystem."), - 'usrquota' => '', + '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"), @@ -154,11 +163,16 @@ sub rationalize { if ($part->{fs_type} ne 'reiserfs') { $options->{notail} = 0; } - if (!fs::type::can_be_one_of_those_fs_types($part, 'vfat', 'smbfs', 'iso9660', 'udf')) { + if (!fs::type::can_be_one_of_those_fs_types($part, 'vfat', 'cifs', 'iso9660', 'udf')) { delete $options->{'codepage='}; } - if (fs::type::directories_needed_to_boot()) { - $options->{$_} = 0 foreach qw(users user noauto supermount); + 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); @@ -166,17 +180,15 @@ sub rationalize { sub set_default { my ($part, %opts) = @_; - #- opts are: useSupermount security iocharset codepage ignore_is_removable + #- 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} =~ /:/)) { - $options->{supermount} = $opts{useSupermount} && !($opts{useSupermount} eq 'magicdev' && $part->{media_type} eq 'cdrom'); - $part->{fs_type} = !$options->{supermount} ? 'auto' : - $part->{media_type} eq 'cdrom' ? 'udf:iso9660' : 'ext2:vfat'; - $options->{sync} = 1 if $part->{media_type} ne 'cdrom'; + $part->{fs_type} = 'auto'; + $options->{flush} = 1 if $part->{media_type} ne 'cdrom'; } if ($part->{media_type} eq 'cdrom') { @@ -185,22 +197,24 @@ sub set_default { if ($part->{media_type} eq 'fd') { # slow device so do not loose time, write now! - $options->{sync} = 1; + $options->{flush} = 1; } - if (isTrueFS($part)) { + 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 - #- file system (e.g, for faster access on the + #- filesystem (e.g, for faster access on the #- news spool to speed up news servers). - $options->{noatime} = detect_devices::isLaptop(); + $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 'smbfs') { + if ($part->{fs_type} eq 'cifs') { add2hash($options, { 'username=' => '%' }) if !$options->{'credentials='}; } if (fs::type::can_be_this_fs_type($part, 'vfat')) { @@ -210,27 +224,31 @@ sub set_default { }) if $part->{is_removable}; put_in_hash($options, { - 'umask=0' => $opts{security} < 3, 'umask=0022' => $opts{security} < 4, + '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} < 3, 'umask=0022' => $opts{security} < 4, + 'umask=0' => $opts{security} < 1, 'umask=0022' => $opts{security} < 2, }); } if (fs::type::can_be_this_fs_type($part, 'iso9660')) { - put_in_hash($options, { user => 1, noexec => 0, 'iocharset=' => $opts{iocharset} }); + 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->{supermount}) { + if ($options->{autofs}) { $options->{users} = $options->{user} = 0; } @@ -248,7 +266,7 @@ sub set_default { sub set_all_default { my ($all_hds, %opts) = @_; - #- opts are: useSupermount security iocharset codepage + #- opts are: security iocharset codepage foreach my $part (fs::get::really_all_fstab($all_hds)) { set_default($part, %opts); 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 index caada6dfb..141d5b5e2 100644 --- a/perl-install/fs/type.pm +++ b/perl-install/fs/type.pm @@ -1,15 +1,25 @@ -package fs::type; # $Id$ +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( - isEmpty isExtended isTrueLocalFS isTrueFS isDos isSwap isOtherAvailableFS isRawLVM isRawRAID isRAID isLVM isMountableRW isNonMountable isPartOfLVM isPartOfRAID isPartOfLoopback isLoopback isMounted isBusy isSpecial isApple isAppleBootstrap isWholedisk isHiddenMacPart isFat_or_NTFS - maybeFormatted set_isFormatted + 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 ); @@ -21,24 +31,25 @@ my (%type_name2pt_type, %type_name2fs_type, %fs_type2pt_type, %pt_type2fs_type, 0x82 => 'swap', 'Linux swap', 0x83 => 'ext2', 'Linux native', 0x83 => 'ext3', 'Journalised FS: ext3', - 0x83 => 'reiserfs', 'Journalised FS: ReiserFS', -if_(arch() =~ /ppc|i.86|ia64|x86_64/, - 0x83 => 'xfs', 'Journalised FS: XFS', + 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() =~ /ppc|i.86/, +if_(arch() =~ /i.86|x86_64/, + 0x83 => 'xfs', 'Journalised FS: XFS', 0x83 => 'jfs', 'Journalised FS: JFS', -), -if_(arch() =~ /i.86|ia64|x86_64/, 0x0b => 'vfat', 'FAT32', -), -if_(arch() =~ /ppc/, - 0x401 => '', 'Apple Bootstrap', - 0x402 => 'hfs', 'Apple HFS Partition', - 0x41 => '', 'PPC PReP Boot', + 0x07 => 'ntfs-3g', 'NTFS-3G', + 0x07 => 'ntfs', 'NTFS', + 0x07 => 'ntfs3', 'NTFS3', + 0x07 => 'exfat', 'exFAT', ), ], - less_important => [ + non_fs_type => [ + 0x83 => '', 'Encrypted', 0x8e => '', 'Linux Logical Volume Manager', 0xfd => '', 'Linux RAID', ], @@ -50,34 +61,14 @@ if_(arch() =~ /ppc/, 0x85 => '', 'Linux extended', ], - backward_compatibility => [ - 0x183 => 'reiserfs', 'reiserfs (deprecated)', - 0x283 => 'xfs', 'xfs (deprecated)', - 0x383 => 'jfs', 'jfs (deprecated)', - 0x483 => 'ext3', 'ext3 (deprecated)', - ], - other => [ - if_(arch() =~ /^ia64/, - 0x100 => '', 'Various', -), if_(arch() =~ /^ppc/, - 0x401 => 'apple', 'Apple Partition', -), if_(arch() =~ /^sparc/, - 0x01 => 'ufs', 'SunOS boot', - 0x02 => 'ufs', 'SunOS root', - 0x03 => '', 'SunOS swap', - 0x04 => 'ufs', 'SunOS usr', - 0x05 => '', 'Whole disk', - 0x06 => 'ufs', 'SunOS stand', - 0x07 => 'ufs', 'SunOS var', - 0x08 => 'ufs', 'SunOS home', -), if_(arch() =~ /^i.86|x86_64/, + if_(arch() =~ /^i.86|x86_64/, 0x01 => 'vfat', 'FAT12', 0x02 => '', 'XENIX root', 0x03 => '', 'XENIX usr', 0x04 => 'vfat', 'FAT16 <32M', 0x06 => 'vfat', 'FAT16', - 0x07 => (arch() =~ /^ppc/ ? 'hpfs' : 'ntfs'), 'NTFS (or HPFS)', + 0x07 => 'hpfs', 'HPFS', 0x08 => '', 'AIX', ), 0x09 => '', 'AIX bootable', @@ -91,16 +82,14 @@ if_(arch() =~ /ppc/, 0x16 => '', 'Hidden FAT16', 0x17 => 'ntfs', 'Hidden HPFS/NTFS', 0x18 => '', 'AST SmartSleep', - 0x1b => 'vfat', 'Hidden W95 FAT32', - 0x1c => 'vfat', 'Hidden W95 FAT32 (LBA)', - 0x1e => 'vfat', 'Hidden W95 FAT16 (LBA)', + 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', -if_(arch() !~ /ppc/, 0x41 => '', 'PPC PReP Boot', -), 0x42 => '', 'SFS', 0x4d => '', 'QNX4.x', 0x4e => '', 'QNX4.x 2nd part', @@ -121,9 +110,12 @@ if_(arch() !~ /ppc/, 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', - 0x87 => '', 'NTFS volume set ', + 0x86 => '', 'NTFS volume set (0x86)', + 0x87 => '', 'NTFS volume set (0x87)', 0x93 => '', 'Amoeba', 0x94 => '', 'Amoeba BBT', 0x9f => '', 'BSD/OS', @@ -138,6 +130,7 @@ if_(arch() !~ /ppc/, 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)', @@ -151,7 +144,6 @@ if_(arch() !~ /ppc/, 0xe4 => '', 'SpeedStor (FAT-16)', 0xeb => 'befs', 'BeOS fs', 0xee => '', 'EFI GPT', - 0xef => 'vfat', 'EFI (FAT-12/16/32)', 0xf0 => '', 'Linux/PA-RISC boot', 0xf4 => '', 'SpeedStor (large part.)', 0xf2 => '', 'DOS secondary', @@ -178,16 +170,26 @@ if_(arch() !~ /ppc/, } -sub type_names() { +sub type_names { + my ($expert, $o_hd) = @_; my @l = @{$type_names{important}}; - push @l, @{$type_names{less_important}}, sort @{$type_names{other}} if $::expert; - @l; + 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} && - { fs_type => $type_name2fs_type{$name}, pt_type => $type_name2pt_type{$name} }; + { type_name => $name, + fs_type => $type_name2fs_type{$name}, pt_type => $type_name2pt_type{$name} }; } sub part2type_name { @@ -248,25 +250,6 @@ sub set_type_subpart { } } - -my @partitions_signatures = ( - (map { [ 'Linux Logical Volume Manager', 0x200 * $_ + 0x18, "LVM2" ] } 0 .. 3), - [ 'Linux Logical Volume Manager', 0, "HM\1\0" ], - [ 'ext2', 0x438, "\x53\xEF" ], - [ 'reiserfs', 0x10034, "ReIsErFs" ], - [ 'reiserfs', 0x10034, "ReIsEr2Fs" ], - [ 'xfs', 0, 'XFSB', 0x200, 'XAGF', 0x400, 'XAGI' ], - [ 'jfs', 0x8000, 'JFS1' ], - [ 'swap', 4086, "SWAP-SPACE" ], - [ 'swap', 4086, "SWAPSPACE2" ], - [ 'ntfs', 0x1FE, "\x55\xAA", 0x3, "NTFS" ], - [ 'FAT32', 0x1FE, "\x55\xAA", 0x52, "FAT32" ], -if_(arch() !~ /^sparc/, - [ 'FAT16', 0x1FE, "\x55\xAA", 0x36, "FAT" ], - [ 'iso9660', 0x8001, "CD001" ], -), -); - sub fs_type_from_magic { my ($part) = @_; if (exists $part->{fs_type_from_magic}) { @@ -277,71 +260,144 @@ sub fs_type_from_magic { } } +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 $dev = devices::make($part->{device}); - - my $check_md = sub { - my ($F) = @_; - my $MD_RESERVED_SECTORS = 128; - my $sector = round_down($part->{size}, $MD_RESERVED_SECTORS) - $MD_RESERVED_SECTORS; #- MD_NEW_SIZE_SECTORS($part->{size}) - if (c::lseek_sector(fileno $F, $sector, 0)) { - my $tmp; - my $signature = "\xfc\x4e\x2b\xa9"; - sysread($F, $tmp, length $signature); - $tmp eq $signature and return "Linux RAID"; - } - ''; - }; - my $t = typeFromMagic($dev, - if_($part->{size}, $check_md), - @partitions_signatures) or return; - - my $p = type_name2subpart($t) || fs_type2subpart($t) || internal_error("unknown name/fs $t"); - if ($p->{fs_type} eq 'ext2') { - #- there is no magic to differentiate ext3 and ext2. Using libext2fs - #- to check if it has a journal - $p->{fs_type} = 'ext3' if c::is_ext3($dev); + 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}"); } - $part->{fs_type_from_magic} = $p->{fs_type}; $p; } - -sub isEmpty { !$_[0]{fs_type} && $_[0]{pt_type} == 0 } -sub isEfi { arch() =~ /ia64/ && $_[0]{pt_type} == 0xef } -sub isWholedisk { arch() =~ /^sparc/ && $_[0]{pt_type} == 5 } -sub isExtended { arch() !~ /^sparc/ && ($_[0]{pt_type} == 5 || $_[0]{pt_type} == 0xf || $_[0]{pt_type} == 0x85) } -sub isRawLVM { $_[0]{pt_type} == 0x8e } -sub isRawRAID { $_[0]{pt_type} == 0xfd } +# 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 { arch() !~ /^sparc/ && ${{ 1 => 1, 4 => 1, 6 => 1 }}{$_[0]{pt_type}} } -sub isFat_or_NTFS { member($_[0]{fs_type}, 'vfat', 'ntfs') } +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 isHiddenMacPart { defined $_[0]{isMap} } +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' } -sub isTrueFS { isTrueLocalFS($_[0]) || member($_[0]{fs_type}, qw(nfs)) } -sub isTrueLocalFS { member($_[0]{fs_type}, qw(ext2 reiserfs xfs jfs ext3)) } +=item isTrueFS($part) -sub isOtherAvailableFS { isEfi($_[0]) || isFat_or_NTFS($_[0]) || member($_[0]{fs_type}, 'ufs', 'hfs', 'iso9660') } #- other OS that linux can access its filesystem +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) = @_; - isRawRAID($part) || isRawLVM($part) || $part->{fs_type} eq 'ntfs' && !$part->{isFormatted} && $part->{notFormatted}; + 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/ } +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]) || isPartOfLoopback($_[0]) } +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); @@ -354,7 +410,7 @@ sub can_be_one_of_those_fs_types { sub maybeFormatted { my ($part) = @_; - $part->{isFormatted} || !$part->{notFormatted} && !$part->{bad_fs_type_magic}; + $part->{isFormatted} || !$part->{notFormatted} && (!$part->{bad_fs_type_magic} || $part->{options} =~ /encrypted/); } sub set_isFormatted { my ($part, $val) = @_; @@ -364,13 +420,37 @@ sub set_isFormatted { delete $part->{fs_type_from_magic}; } -#- do this before modifying $part->{fs_type} +=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} < 16 << 11 and die N("You can not use JFS for partitions smaller than 16MB"); - $fs_type eq "reiserfs" && $part->{size} < 32 << 11 and die N("You can not use ReiserFS for partitions smaller than 32MB"); + $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 directories_needed_to_boot() { +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; |