#!/usr/bin/perl use lib qw(/usr/lib/libDrakX); # prevent firing up drakbug (doesn't work w/o X11): BEGIN { $ENV{DISABLE_DRAKBUG} = 1 } use strict; use diagnostics; # fix translating reasons for 2nd stage warning use lang; BEGIN { my $locale = lang::read($>); lang::set($locale); } use standalone; #- warning, standalone must be loaded very first, for 'explanations' use c; use common; use interactive; use detect_devices; use harddrake::data; use harddrake::autoconf; use harddrake::sound; use Xconfig::card; use Xconfig::various; use modules; use Storable qw(store retrieve); my $force = member('--force', @ARGV); my $reboot_needed; my $do_udev_settle; my $invert_do_it = $ARGV[0] eq 'X11' ? 1 : 0; my ($hw_sysconfdir, $timeout) = ("/etc/sysconfig/harddrake2", $invert_do_it ? 600 : 25); my $last_boot_config = "$hw_sysconfdir/previous_hw"; $last_boot_config .= '_X11' if $invert_do_it; # first run ? if not read old hw config my $previous_config; if (-f $last_boot_config && -s $last_boot_config) { eval { $previous_config = Storable::retrieve($last_boot_config) }; log::explanations("resetting previous hardware file ($@)") if $@; } $previous_config ||= {}; $previous_config = $$previous_config if ref($previous_config) !~ /HASH/; my $first_run = is_empty_hash_ref($previous_config); my $modules_conf = modules::any_conf->read; my $isLaptop = detect_devices::isLaptop(); my $curr_kernel = c::kernel_version(); my ($kernel_major) = $curr_kernel =~ /^(\d+\.\d+)/; my %previous_kernel_config = getVarsFromSh("$hw_sysconfdir/kernel"); my %previous_xorg_config = getVarsFromSh("$hw_sysconfdir/xorg"); setVarsInSh("$hw_sysconfdir/kernel", { KERNEL => $kernel_major, IS_LAPTOP => bool2text($isLaptop) }); my %cfg = getVarsFromSh("$hw_sysconfdir/service.conf"); # default to 'yes' on upgrade: $cfg{AUTORECONFIGURE_RIGHT_XORG_DRIVER} = 'yes' if !exists $cfg{AUTORECONFIGURE_RIGHT_XORG_DRIVER}; $cfg{HANDLE_KMS_BOOT_CONFIG} = 'yes' if !exists $cfg{HANDLE_KMS_BOOT_CONFIG}; # autoreconfigure laptop-dependent services when switching from laptop to desktop, and vice versa if (!exists $previous_kernel_config{IS_LAPTOP} || $force || $isLaptop != text2bool($previous_kernel_config{IS_LAPTOP})) { log::explanations("Autoconfiguring laptop tools since we switched between laptop and desktop systems"); harddrake::autoconf::laptop($isLaptop); } my $known_kernels = "$hw_sysconfdir/kernels"; if (!member($curr_kernel, chomp_(cat_($known_kernels)))) { harddrake::autoconf::fix_aliases($modules_conf) if !$first_run; append_to_file($known_kernels, "$curr_kernel\n"); } sub dialog_with_timeout { my ($type, $title, $msg, $timeout, $plymouth) = @_; my ($pid, $res, $timed_out); $SIG{ALRM} = sub { $timed_out = 1; kill 15, $pid }; unless ($pid = fork()) { $plymouth and system('plymouth', 'hide-splash'); exec("/usr/share/harddrake/confirm", $type, $title, $msg); } alarm($timeout); wait(); $res = $?; alarm(0); return $res, $timed_out; } sub get_xorg_driver() { my $x = Xconfig::xfree->read; if ($x) { my ($dev_section) = grep { $_->{name} eq 'Device' } @{$x->{raw}}; $dev_section && $dev_section->{l}{Driver}{val}; } } sub schedule_warn_about_switch { my ($reason) = @_; output('/var/run/harddrake-notify-x11-free-driver-switch', $reason); } my $lib = arch() =~ /x86_64/ ? "lib64" : "lib"; sub find_xorg_driver { my ($new_driver) = @_; # nvidia driver has special place: -e "/usr/$lib/xorg/modules/drivers/${new_driver}_drv.so" || -e "/usr/$lib/xorg/extra-modules/${new_driver}_drv.so"; } sub after_x_driver_switch { # If a wrong driver is loaded, ask for a reboot. my $reboot_needed = -x "/sbin/display_driver_helper" && system("/sbin/display_driver_helper", "--check-loaded") != 0; if (!$reboot_needed) { # Load any new drivers. system("udevadm", "trigger", "--subsystem-match=pci", "--attr-match=class=0x03*"); } $reboot_needed; } sub switch_x_driver { my ($old_driver, $new_driver, $reason) = @_; if (!find_xorg_driver($new_driver)) { log::explanations("would switch X.org driver from '$old_driver' to '$new_driver' ($reason); but new driver is not installed"); return; } # This should use calls to Xconfig instead of substitution. However, currently # Xconfig probably makes too intrusive changes to xorg.conf when switching the driver. cp_af('/etc/X11/xorg.conf', "/etc/X11/xorg.conf.mga$^T"); substInFile { s!Driver "($old_driver)"!Driver "$new_driver"!g } '/etc/X11/xorg.conf'; log::explanations("switch X.org driver from '$old_driver' to '$new_driver' ($reason)"); Xconfig::card::libgl_config_and_more({ Driver => $new_driver }); Xconfig::various::setup_kms(); after_x_driver_switch(); # returns 1 if reboot is needed } sub should_reconfigure_x_driver { my ($card_data, $device, $current_driver) = @_; my $reason; my $reconfigure; my $new_key = $card_data->{Driver} . $card_data->{Driver2}; setVarsInSh("$hw_sysconfdir/xorg", { XORG_DRV => $new_key }); # auto reconfigure x11 only on first time default driver have changed: if ($previous_xorg_config{XORG_DRV} ne $new_key) { if (!member($current_driver, $card_data->{Driver}, $card_data->{Driver2}, 'fbdev', 'vesa')) { $reason = N("The graphic card '%s' is no more supported by the '%s' driver", $device->{description}, $current_driver); $reconfigure = 1; } } elsif ((stat('/etc/X11/xorg.conf'))[9] < (stat('/etc/product.id'))[9]) { # when switching to a new release (product.id is newer than xorg.conf), # regenerate xorg.conf even if the driver used is vesa or fbdev, this # way we handle switches like "no driver for the card in older releases # but good driver in new release", see bug #53753 if (!member($current_driver, $card_data->{Driver}, $card_data->{Driver2})) { $reason = N("New release, reconfiguring X for %s", $device->{description}); $reconfigure = 1; } } ($reconfigure, $reason); } my @cards = ( { ldetect_driver_regexp => 'Card:NVIDIA', xorg_driver_regexp => 'nv.+', module_names => [ qw(NVdriver nvidia.o nvidia.ko nvidia71xx.ko nvidia96xx.ko nvidia97xx.ko nvidia173.ko nvidia-current.ko) ] }, { ldetect_driver_regexp => 'Card:ATI Radeon', xorg_driver_regexp => 'fglrx', module_names => [ qw(fglrx.ko fglrx-hd2000.ko) ] } ); my @devices; @devices = grep { $_->{driver} =~ /^Card:/ } detect_devices::probeall() if -f '/etc/X11/xorg.conf'; # do not auto reconfigure if more than one graphic card: $cfg{AUTORECONFIGURE_RIGHT_XORG_DRIVER} = 'no' if scalar(@devices) > 1; foreach my $device (@devices) { next if !text2bool($cfg{AUTORECONFIGURE_RIGHT_XORG_DRIVER}); my $id = $device->{driver} =~ /Card:(.*)/ && $1; my $card_data = Xconfig::card::readCardsDB("/usr/share/ldetect-lst/Cards+")->{$id}; my $current_driver = get_xorg_driver(); # nvidia proprietary driver in ldetect-lst can be 'nvidia173', 'nvidia-current', ... # but really is just 'nvidia' in xorg.conf: $card_data->{Driver2} =~ s/(nvidia).*/$1/; # auto reconfigure x11 only on first time default driver have changed: my ($should_reconfigure, $reason) = should_reconfigure_x_driver($card_data, $device, $current_driver); if ($should_reconfigure) { if (-e "/tmp/.X11-unix/X0") { # We are too late, X server is already running. # It was probably speedboot, disable it for next boot. substInFile { s!^$curr_kernel .*\n!! } "/var/lib/speedboot/status" if -e "/var/lib/speedboot/status"; # Restore state as we were not able to switch the driver yet. setVarsInSh("$hw_sysconfdir/xorg", { XORG_DRV => $previous_xorg_config{XORG_DRV} }); } else { $reboot_needed |= switch_x_driver($current_driver, $card_data->{Driver}, $reason); $do_udev_settle = 1; schedule_warn_about_switch($reason) if any { $current_driver =~ $_->{xorg_driver_regexp} } @cards; # Update $current_driver with the new one $current_driver = $card_data->{Driver}; } } # nv->nouveau or non_kms_nouveau->kms_nouveau can't have "Disable dri"! if ($current_driver eq "nouveau") { my $raw_x = Xconfig::xfree->read; if ($raw_x) { if (member("dri", $raw_x->get_disabled_modules)) { $raw_x->remove_disable_module("dri"); $raw_x->write; } } } } foreach my $card (@cards) { my $device = find { $_->{driver} =~ /$card->{ldetect_driver_regexp}/ } @devices; next if !$device; if (find { -e join('', "/lib/modules/", c::kernel_version(), $_) } map { ("/dkms/$_", "/dkms-binary/$_", "/kernel/$_") } map { "/drivers/$_" } map { ("extra/$_", "video/$_", "char/$_", "char/drm/$_") } map { $_, "$_.gz" } @{$card->{module_names}}) { # do not automatically switch from nv to nvidia (in order to handle # cases where nvidia module crashes the system): # # substInFile { # log::explanations("switch XFree86 driver from nv to nvidia") if /Driver "nv"/; # s!Driver "nv.*"!Driver "nvidia"!g; # s!#*( Load.*glx)!\1!g; # } $_ foreach "/etc/X11/XF86Config-4", "/etc/X11/XF86Config"; } else { my @cards = Xconfig::card::probe(); my $driver = $cards[0]{Driver}; my $old_driver = cat_('/etc/X11/xorg.conf') =~ /Driver "($card->{xorg_driver_regexp})"/ && $1; if ($old_driver) { my $reason = N("The proprietary kernel driver was not found for '%s' X.org driver", $old_driver); $reboot_needed |= switch_x_driver($card->{xorg_driver_regexp}, $driver, $reason); $do_udev_settle = 1; schedule_warn_about_switch($reason); } } } my $is_globetrotter = -f '/usr/sbin/mdkmove'; my (%config, $wait); my $in; my $plymouth = -x '/bin/plymouth'; # For each hw, class, detect device, compare and offer to reconfigure if needed foreach my $hw_class (@harddrake::data::tree) { my ($Ident, $item, $configurator, $detector, $do_it) = @$hw_class{qw(class string configurator detector checked_on_boot)}; next if member($cfg{"DETECT_$Ident"}, qw(NO no)); $configurator ||= $hw_class->{configurator}; next unless $do_it ^ $invert_do_it; # No detector ? (should never happen but who know ?) ref($detector) eq 'CODE' or next; my %ID = map { my $i = $_; my $id = defined $i->{device} ? $i->{device} : join(':', map { $i->{$_} } qw(vendor id subvendor subid)); $id => $i; } eval { $detector->({}) }; $config{$Ident} = \%ID; next if !$is_globetrotter && !$force && $first_run; # do not fsck on first run but if --force my $oldconfig = $force ? {} : $previous_config->{$Ident}; my $msg; my @was_removed = difference2([ keys %$oldconfig ], [ keys %ID ]); if (@was_removed) { $msg .= N("Some devices in the \"%s\" hardware class were removed:\n", $item) . join('', map { N("- %s was removed\n", harddrake::data::custom_id($oldconfig->{$_}, $item)) } @was_removed) . "\n"; } my @added = difference2([ keys %ID ], [ keys %$oldconfig ]); $msg .= N("Some devices were added: %s\n", $item) if @added; $msg .= N("- %s was added\n", harddrake::data::custom_id($ID{$_}, $item)) foreach @added; log::explanations("removed $Ident: " . harddrake::data::custom_id($oldconfig->{$_}, $item)) foreach @was_removed; log::explanations("added $Ident: " . harddrake::data::custom_id($ID{$_}, $item)) foreach @added; if ($Ident eq 'FIREWIRE_CONTROLLER' && any { $_->{driver} eq 'ohci1394' } @ID{@added}) { modules::load_and_configure($modules_conf, 'ohci1394'); $modules_conf->write; } @added || @was_removed or $cfg{"DETECT_$Ident"} ne 'force' and next; next if $Ident eq 'MOUSE' && $kernel_major ne $previous_kernel_config{KERNEL} && $cfg{"DETECT_$Ident"} ne 'force'; my @configurator_pool = $configurator; if ($Ident eq "AUDIO") { # automatic sound slots configuration rm_rf("/etc/asound.state") if -e "/etc/asound.state"; harddrake::sound::configure_sound_slots($modules_conf); next; } elsif ($Ident eq "ETHERNET") { $modules_conf->remove_alias_regexp('^(wlan|eth)[0-9]*$'); modules::load_category($modules_conf, 'network/main|gigabit|usb|wireless|firewire|pcmcia'); require network::connection::ethernet; network::connection::ethernet::configure_eth_aliases($modules_conf); require network::rfswitch; network::rfswitch::configure(); require network::shorewall; network::shorewall::update_interfaces_list(); $modules_conf->write; next; } elsif (member($Ident, qw(ATA_STORAGE CARD_READER RAID_STORAGE SATA_STORAGE SCSI_CONTROLLER))) { # set scsi_hostadapter in modprobe.conf: modules::load_category($modules_conf, 'disk/' . { ATA_STORAGE => 'ide', SATA_STORAGE => 'sata', SCSI_CONTROLLER => 'scsi', RAID_STORAGE => 'hardware_raid', CARD_READER => 'card_reader' }->{$Ident}); $modules_conf->write; next; } elsif (member($Ident, qw(AGP DVB TV))) { my @old_drivers = uniq(map { $_->{driver} } values %$oldconfig); my @new_drivers = uniq(map { $_->{driver} } values %ID); # load DVB & TV drivers (eg: for One), not for AGP (done by X): modules::load_category($modules_conf, 'multimedia/' . lc($Ident)) if member($Ident, qw(DVB TV)); $modules_conf->remove_module(difference2(\@old_drivers, \@new_drivers)); # add agpgart and the like modules to modprobe.preload if needed: $modules_conf->write; foreach (difference2(\@new_drivers, \@old_drivers)) { eval { modules::load($_) }; warn "warning: $@" if $@; } next; } elsif ($Ident eq "BLUETOOTH") { harddrake::autoconf::bluetooth(scalar keys %ID); } elsif ($Ident eq "PCMCIA_CONTROLLER") { harddrake::autoconf::pcmcia(keys %ID ? first(values(%ID))->{driver} : ''); } elsif ($Ident eq "USB_CONTROLLER") { # nearly useless (only mkinitrd uses it): modules::load_category($modules_conf, 'bus/usb'); $modules_conf->write; } elsif ($Ident eq "VIDEO") { # explicitely NOT read the existing config (eg: new profile with globetrotter) harddrake::autoconf::xconf($modules_conf, {}, member($cfg{SETUP_FB}, qw(NO no)), $cfg{RESOLUTION_WANTED}); $reboot_needed |= after_x_driver_switch(); $do_udev_settle = 1; next; } elsif ($Ident eq "MOUSE") { harddrake::autoconf::mouse_conf($modules_conf); next; } elsif ($Ident eq "CPU") { harddrake::autoconf::cpufreq(); } elsif ($Ident eq "FLOPPY") { harddrake::autoconf::floppy(); } next if $is_globetrotter && !$hw_class->{automatic}; next unless $configurator_pool[0]; if (ref($configurator) ne 'CODE' && !-x first(split /\s+/, $configurator_pool[0])) { log::explanations(qw(skip $Ident configuration since "$configurator" is not executable)); next; } my ($no, $res); $hw_class->{automatic} ||= ref($configurator) eq 'CODE'; if (!$hw_class->{automatic}) { ($res, $no) = dialog_with_timeout("yesorno", N("Hardware changes in \"%s\" class (%s seconds to answer)", $Ident, $timeout), $msg . N("Do you want to run the appropriate config tool?"), $timeout, $plymouth); } else { $res = 1; } if (ref($configurator) eq 'CODE') { eval { $configurator->() }; log::explanations(qw(cannot run "$configurator": $@)) if $@; } elsif (!$no && $res) { foreach my $program (@configurator_pool) { if (fork()) { wait(); } else { log::explanations(qq(run "$program")); exec("$program 2>/dev/null") or do { log::explanations(qq(cannot run "$program")); require POSIX; POSIX::_exit(); }; } } } if (!$hw_class->{automatic}) { require interactive; undef $wait; $in ||= interactive->vnew; $wait = $in->wait_message(N("Please wait"), N("Hardware probing in progress")); } } # output new hw config log::explanations("created file $last_boot_config"); Storable::store(\%config, $last_boot_config); if (!$reboot_needed && text2bool($cfg{HANDLE_KMS_BOOT_CONFIG})) { if (-x "/sbin/display_driver_helper" && system("display_driver_helper", "--check-loaded") != 0) { # incorrect driver is loaded, X.org can't start (e.g. proprietary driver loaded while free driver in use, # or free driver loaded while vesa is configured). $reboot_needed |= Xconfig::various::setup_kms(); } elsif (-e "/dev/.late_kms") { # initrd didn't load the KMS driver; non-fatal but ugly, fix it for next boot unlink("/dev/.late_kms"); Xconfig::various::setup_kms(); } } # Handle $reboot_needed from earlier: my ($reply, $timedout); # Don't do autoreboot if X was somehow already started (not normally the case). if ($reboot_needed && ! -e "/tmp/.X11-unix/X0") { ($reply, $timedout) = dialog_with_timeout("okcancel", N("Display driver setup"), N("The system has to be rebooted due to a display driver change.") . "\n\n" . N("Press Cancel within %d seconds to abort.", 30), 30, $plymouth); if ($reply || $timedout) { exec("/sbin/reboot"); } } system("udevadm", "settle", "--timeout=10") if $do_udev_settle; $in->exit(0) if $in;