diff options
Diffstat (limited to 'lib/network/thirdparty.pm')
-rw-r--r-- | lib/network/thirdparty.pm | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/lib/network/thirdparty.pm b/lib/network/thirdparty.pm new file mode 100644 index 0000000..8fd48ab --- /dev/null +++ b/lib/network/thirdparty.pm @@ -0,0 +1,385 @@ +package network::thirdparty; + +use strict; +use common; +use detect_devices; +use run_program; +use services; +use fs::get; +use fs; +use log; +use modules; +use list_modules; + +#- using bsd_glob() since glob("/DONT_EXIST") return "/DONT_EXIST" instead of () (and we don't want this) +use File::Glob ':glob'; + +#- network_settings is an hash of categories (rtc, dsl, wireless, ...) +#- each category is an hash of device settings + +#- a device settings element must have the following fields: +#- o matching: +#- specify if this settings element matches a driver +#- can be a regexp, array ref or Perl code (parameters: driver) +#- o description: +#- full name of the device +#- o name: name used by the packages + +#- the following fields are optional: +#- o url: +#- url where the user can find tools/drivers/firmwares for this device +#- o device: +#- device in /dev to be configured +#- o post: +#- command to be run after all packages are installed +#- can be a shell command or Perl code +#- o restart_service: +#- if exists but not 1, name of the service to be restarted +#- if 1, specify that the service named by the name field should be restarted +#- o tools: +#- hash of the tools settings +#- test_file field required +#- if package field doesn't exist, 'name' is used +#- o kernel_module: +#- if exists but not 1, hash of the module settings +#- if 1, kernel modules are needed and use the name field +#- (name-kernel or dkms-name) +#- o firmware: +#- hash of the firmware settings +#- test_file field required +#- if package field doesn't exist, 'name-firmware' is used + +#- hash of package settings structure: +#- o package: +#- name of the package to be installed for these device +#- o test_file: +#- file used to test if the package is installed +#- o prefix: +#- path of the files that are tested +#- o links: +#- useful links for this device +#- can be a single link or array ref +#- o user_install: +#- function to call if the package installation fails +#- o explanations: +#- additionnal text to display if the installation fails +#- o no_distro_package: +#- 1 if the package isn't available in the official distribution +# (because of missing distribution rights for example) + +our $firmware_directory = "/lib/firmware"; +our @thirdparty_types = qw(kernel_module tools firmware); + +sub device_get_packages { + my ($settings, $component, $o_default) = @_; + $settings->{$component} or return; + my $package; + if (ref $settings->{$component} eq 'HASH') { + $package = $settings->{$component}{package} || 1; + } else { + $package = $settings->{$component}; + } + $package == 1 ? $o_default || $settings->{name} : ref $package eq 'ARRAY' ? @$package : $package; +} + +sub device_get_option { + my ($settings, $option, $o_default) = @_; + $settings->{$option} or return; + my $value = $settings->{$option}; + $value == 1 ? $o_default || $settings->{name} : $value; +} + +sub component_get_option { + my ($settings, $component, $option) = @_; + ref $settings->{$component} eq 'HASH' && $settings->{$component}{$option} || $settings->{$option}; +} + +sub find_settings { + my ($settings_list, $driver) = @_; + find { + my $match = $_->{matching} || $_->{name}; + my $type = ref $match; + $type eq 'Regexp' && $driver =~ $match || + $type eq 'CODE' && $match->($driver) || + $type eq 'ARRAY' && member($driver, @$match) || + $driver eq $match; + } @$settings_list; +} + +sub device_run_command { + my ($settings, $driver, $component) = @_; + my $command = $settings->{$component} or return; + + if (ref $command eq 'CODE') { + $command->($driver); + } else { + log::explanations("Running $component command $command"); + run_program::rooted($::prefix, $command); + } +} + +sub warn_not_installed { + my ($in, @packages) = @_; + $in->ask_warn(N("Error"), N("Could not install the packages (%s)!", join(', ', @packages))); +} + +sub get_checked_element { + my ($settings, $driver, $component) = @_; + $component eq 'firmware' ? + get_firmware_path($settings) : + $component eq 'kernel_module' ? + $driver : + ref $settings->{$component} eq 'HASH' && $settings->{$component}{test_file}; +} + +sub warn_not_found { + my ($in, $settings, $driver, $component, @packages) = @_; + my %opt; + $opt{$_} = component_get_option($settings, $component, $_) foreach qw(url explanations no_distro_package no_package); + my $checked = get_checked_element($settings, $driver, $component); + my $component_name = ref $settings->{$component} eq 'HASH' && translate($settings->{$component}{component_name}) || $component; + $in->ask_warn(N("Error"), + join(" ", + ($opt{no_package} ? + N("Some components (%s) are required but aren't available for %s hardware.", $component_name, $settings->{name}) : + N("Some packages (%s) are required but aren't available.", join(', ', @packages))), + join("\n\n", + if_(!$opt{no_distro_package} && !$opt{no_package}, + #-PO: first argument is a list of Mageia distributions + #-PO: second argument is a package media name + N("These packages can be found in %s, or in the official %s package repository.", "non-free"), + ), + if_($checked, N("The following component is missing: %s", $checked)), + if_($opt{explanations}, translate($opt{explanations})), + if_($opt{url}, N("The required files can also be installed from this URL: +%s", $opt{url})), + ))); +} + +sub is_file_installed { + my ($settings, $component) = @_; + my $file = ref $settings->{$component} eq 'HASH' && $settings->{$component}{test_file}; + $file && -e "$::prefix$file"; +} + +sub is_module_installed { + my ($settings, $driver) = @_; + my $module = ref $settings->{kernel_module} eq 'HASH' && $settings->{kernel_module}{test_file} || $driver; + #- reload modules.dep so that newly added dkms modules are recognized + list_modules::load_default_moddeps(); + #- FIXME: modules::module_is_available() won't use the chroot modules.dep in installer + modules::module_is_available($module); +} + +sub get_firmware_path { + my ($settings) = @_; + my $wildcard = ref $settings->{firmware} eq 'HASH' && $settings->{firmware}{test_file} or return; + my $path = $settings->{firmware}{prefix} || $firmware_directory; + "$::prefix$path/$wildcard"; +} + +sub is_firmware_installed { + my ($settings) = @_; + my $pattern = get_firmware_path($settings) or return; + scalar bsd_glob($pattern, undef); +} + +sub extract_firmware { + my ($settings, $in) = @_; + my $choice; + $in->ask_from('', N("Firmware files are required for this device."), + [ { type => "list", val => \$choice, format => \&translate, + list => [ + if_(exists $settings->{firmware}{extract}{floppy_source}, N_("Use a floppy")), + if_(exists $settings->{firmware}{extract}{windows_source}, N_("Use my Windows partition")), + N_("Select file") + ] } ]) or return; + my ($h, $source); + if ($choice eq N_("Use a floppy")) { + $source = $settings->{firmware}{extract}{floppy_source}; + $h = find_file_on_floppy($in, $source); + } elsif ($choice eq N_("Use my Windows partition")) { + $source = $settings->{firmware}{extract}{windows_source}; + $h = find_file_on_windows_system($in, $source); + } else { + $source = $settings->{firmware}{extract}{default_source}; + $h = { file => $in->ask_file(N("Please select the firmware file (for example: %s)", basename($source)), dirname($source)) }; + } + if (!-e $h->{file}) { + log::explanations("Unable to find firmware file (tried to find $source."); + return; + } + + if ($settings->{firmware}{extract}{name}) { + $in->do_pkgs->ensure_is_installed($settings->{firmware}{extract}{name}, $settings->{firmware}{extract}{test_file}) or return; + } + $settings->{firmware}{extract}{run}->($h->{file}); + 1; +} + +sub find_file_on_windows_system { + my ($in, $file) = @_; + my $source; + require fsedit; + my $all_hds = fsedit::get_hds(); + fs::get_info_from_fstab($all_hds); + if (my $part = find { $_->{device_windobe} eq 'C' } fs::get::fstab($all_hds)) { + foreach (qw(windows/system winnt/system windows/system32/drivers winnt/system32/drivers)) { + -d $_ and $source = first(bsd_glob("$part->{mntpoint}/$_/$file", undef)) and last; + } + $source or $in->ask_warn(N("Error"), N("Unable to find \"%s\" on your Windows system!", $file)); + } else { + $in->ask_warn(N("Error"), N("No Windows system has been detected!")); + } + { file => $source }; +} + +sub find_file_on_floppy { + my ($in, $file) = @_; + my $floppy = detect_devices::floppy(); + my $mountpoint = '/media/floppy'; + my $h; + $in->ask_okcancel(N("Insert floppy"), + N("Insert a FAT formatted floppy in drive %s with %s in root directory and press %s", $floppy, $file, N("Next"))) or return; + if (eval { fs::mount::mount(devices::make($floppy), $mountpoint, 'vfat', 'readonly'); 1 }) { + log::explanations("Mounting floppy device $floppy in $mountpoint"); + $h = before_leaving { fs::mount::umount($mountpoint) }; + if ($h->{file} = first(bsd_glob("$mountpoint/$file", undef))) { + log::explanations("Found $h->{file} on floppy device"); + } else { + log::explanations("Unabled to find $file on floppy device"); + } + } else { + $in->ask_warn(N("Error"), N("Floppy access error, unable to mount device %s", $floppy)); + log::explanations("Unable to mount floppy device $floppy"); + } + $h; +} + +sub get_required_packages { + my ($type, $settings) = @_; + device_get_packages($settings, $type, if_($type eq 'firmware', "$settings->{name}-firmware")); +} + +sub check_installed { + my ($type, $settings, $driver) = @_; + $type eq 'kernel_module' ? + is_module_installed($settings, $driver) : + $type eq 'firmware' ? + is_firmware_installed($settings) : + is_file_installed($settings, $type); +} + +sub get_available_packages { + my ($type, $do_pkgs, @names) = @_; + if ($type eq 'kernel_module') { + return map { my $l = $do_pkgs->check_kernel_module_packages($_); $l ? @$l : () } @names; + } else { + return $do_pkgs->is_available(@names); + } +} + +sub user_install { + my ($type, $settings, $in) = @_; + if ($type eq 'firmware') { + ref $settings->{$type} eq 'HASH' or return; + if ($settings->{$type}{extract}) { + extract_firmware($settings, $in); + } else { + my $f = $settings->{$type}{user_install}; + $f && $f->($settings, $in); + } + } else { + my $f = ref $settings->{$type} eq 'HASH' && $settings->{$type}{user_install}; + $f && $f->($settings, $in); + } +} + +sub install_packages { + my ($in, $settings, $driver, $component, @packages) = @_; + + unless (@packages) { + log::explanations(qq(No $component package for module "$driver" is required, skipping)); + return 1; + } + + if (check_installed($component, $settings, $driver)) { + $settings->{old_status}{$component} = 1; + log::explanations(qq(Required $component package for module "$driver" is already installed, skipping)); + return 1; + } + + my $optional = ref $settings->{$component} eq 'HASH' && $settings->{$component}{optional}; + if (my @available = get_available_packages($component, $in->do_pkgs, @packages)) { + log::explanations("Installing thirdparty packages ($component) " . join(', ', @available)); + if ($in->do_pkgs->install(@available) && check_installed($component, $settings, $driver)) { + return 1; + } elsif (!$optional) { + warn_not_installed($in, @available); + } + } + return 1 if $optional; + log::explanations("Thirdparty package @packages ($component) is required but not available"); + + 0; +} + +sub install_components { + my ($in, $settings, $driver, @components) = @_; + + foreach my $component (@components) { + my @packages = get_required_packages($component, $settings); + if (!component_get_option($settings, $component, 'no_package')) { + install_packages($in, $settings, $driver, $component, @packages) and next; + } + + unless (user_install($component, $settings, $in)) { + warn_not_found($in, $settings, $driver, $component, @packages); + return; + } + } + + 1; +} + +sub apply_settings { + my ($in, $category, $settings_list, $driver) = @_; + + my $settings = find_settings($settings_list, $driver); + if ($settings) { + log::explanations(qq(Found settings for driver "$driver" in category "$category")); + + my $wait = $in->wait_message(N("Please wait"), N("Looking for required software and drivers...")); + + install_components($in, $settings, $driver, @thirdparty_types) or return; + + if (!$settings->{no_module_reload}) { + if (exists $settings->{firmware} && !$settings->{old_status}{firmware}) { + log::explanations("Reloading module $driver"); + eval { modules::unload($driver) }; + } else { + log::explanations("Loading module $driver"); + } + eval { modules::load($driver) }; + } + + undef $wait; + $wait = $in->wait_message(N("Please wait"), N("Please wait, running device configuration commands...")); + device_run_command($settings, $driver, 'post'); + + if (my $service = device_get_option($settings, 'restart_service')) { + log::explanations("Restarting service $service"); + services::restart_or_start($service); + } + + $settings->{sleep} and sleep $settings->{sleep}; + + log::explanations(qq(Settings for driver "$driver" applied)); + } else { + log::explanations(qq(No settings found for driver "$driver" in category "$category")); + } + + $settings || {}; +} + +1; |