#!/usr/bin/perl -- BEGIN { # Work-around for bug #479711 in perl $ENV{PERL_DL_NONLAZY} = 1; } use strict; use warnings; use POSIX qw(:errno_h); use Dpkg; use Dpkg::Gettext; textdomain("dpkg"); # Global variables: my $altdir = '/etc/alternatives'; my $admdir = $admindir . '/alternatives'; my $action = ''; # Action to perform (display / query / install / remove / auto / config) my $alternative; # Alternative worked on my $inst_alt; # Alternative to install my $fileset; # Set of files to install in the alternative my $path; # Path of alternative we are offering my $log_file = "/var/log/dpkg.log"; my $skip_auto = 0; # Skip alternatives properly configured in auto mode (for --config) my $verbosemode = 0; my $force = 0; my @pass_opts; $| = 1; # # Main program # my @COPY_ARGV = @ARGV; while (@ARGV) { $_ = shift(@ARGV); last if m/^--$/; if (!m/^--/) { error(_g("unknown argument \`%s'"), $_); } elsif (m/^--help$/) { usage(); exit(0); } elsif (m/^--version$/) { version(); exit(0); } elsif (m/^--verbose$/) { $verbosemode= +1; push @pass_opts, $_; } elsif (m/^--quiet$/) { $verbosemode= -1; push @pass_opts, $_; } elsif (m/^--install$/) { set_action("install"); @ARGV >= 4 || badusage(_g("--install needs ")); my $link = shift @ARGV; my $name = shift @ARGV; my $path = shift @ARGV; my $priority = shift @ARGV; badusage(_g(" and can't be the same")) if $link eq $path; $priority =~ m/^[-+]?\d+/ || badusage(_g("priority must be an integer")); $alternative = Alternative->new($name); $inst_alt = Alternative->new($name); $inst_alt->set_status("auto"); $inst_alt->set_link($link); $fileset = FileSet->new($path, $priority); } elsif (m/^--(remove|set)$/) { set_action($1); @ARGV >= 2 || badusage(_g("--%s needs "), $1); $alternative = Alternative->new(shift(@ARGV)); $path = shift @ARGV; } elsif (m/^--(display|query|auto|config|list|remove-all)$/) { set_action($1); @ARGV || badusage(_g("--%s needs "), $1); $alternative = Alternative->new(shift(@ARGV)); } elsif (m/^--(all|get-selections|set-selections)$/) { set_action($1); } elsif (m/^--slave$/) { badusage(_g("--slave only allowed with --install")) unless $action eq "install"; @ARGV >= 3 || badusage(_g("--slave needs ")); my $slink = shift @ARGV; my $sname = shift @ARGV; my $spath = shift @ARGV; badusage(_g(" and can't be the same")) if $slink eq $spath; badusage(_g("name %s is both primary and slave"), $inst_alt->name()) if $sname eq $inst_alt->name(); if ($inst_alt->has_slave($sname)) { badusage(_g("slave name %s duplicated"), $sname); } foreach my $slave ($inst_alt->slaves()) { my $link = $inst_alt->slave_link($slave) || ""; badusage(_g("slave link %s duplicated"), $slink) if $link eq $slink; badusage(_g("link %s is both primary and slave"), $slink) if $link eq $inst_alt->link(); } $inst_alt->add_slave($sname, $slink); $fileset->add_slave($sname, $spath); } elsif (m/^--log$/) { @ARGV || badusage(_g("--%s needs a argument"), "log"); $log_file = shift @ARGV; push @pass_opts, $_, $log_file; } elsif (m/^--altdir$/) { @ARGV || badusage(_g("--%s needs a argument"), "altdir"); $altdir = shift @ARGV; push @pass_opts, $_, $altdir; } elsif (m/^--admindir$/) { @ARGV || badusage(_g("--%s needs a argument"), "admindir"); $admdir = shift @ARGV; push @pass_opts, $_, $admdir; } elsif (m/^--skip-auto$/) { $skip_auto = 1; push @pass_opts, $_; } elsif (m/^--force$/) { $force = 1; push @pass_opts, $_; } else { badusage(_g("unknown option \`%s'"), $_); } } badusage(_g("need --display, --query, --list, --get-selections, --config," . "--set, --set-selections, --install, --remove, --all, " . "--remove-all or --auto")) unless $action; # Load infos about all alternatives to be able to check for mistakes my %ALL; foreach my $alt_name (get_all_alternatives()) { my $alt = Alternative->new($alt_name); next unless $alt->load("$admdir/$alt_name", 1); $ALL{objects}{$alt_name} = $alt; $ALL{links}{$alt->link()} = $alt_name; $ALL{parent}{$alt_name} = $alt_name; foreach my $slave ($alt->slaves()) { $ALL{links}{$alt->slave_link($slave)} = $slave; $ALL{parent}{$slave} = $alt_name; } } # Check that caller don't mix links between alternatives and don't mix # alternatives between slave/master, and that the various parameters # are fine if ($action eq "install") { my ($name, $link, $file) = ($inst_alt->name(), $inst_alt->link(), $fileset->master()); if (exists $ALL{parent}{$name} and $ALL{parent}{$name} ne $name) { error(_g("alternative %s can't be master: %s"), $name, sprintf(_g("it is a slave of %s"), $ALL{parent}{$name})); } if (exists $ALL{links}{$link} and $ALL{links}{$link} ne $name) { error(_g("alternative link %s is already managed by %s."), $link, $ALL{parent}{$ALL{links}{$link}}); } error(_g("alternative link is not absolute as it should be: %s"), $link) unless $link =~ m|^/|; error(_g("alternative path is not absolute as it should be: %s"), $file) unless $file =~ m|^/|; error(_g("alternative path %s doesn't exist."), $file) unless -e $file; error(_g("alternative name (%s) must not contain '/' and spaces."), $name) if $name =~ m|[/\s]|; foreach my $slave ($inst_alt->slaves()) { $link = $inst_alt->slave_link($slave); $file = $fileset->slave($slave); if (exists $ALL{parent}{$slave} and $ALL{parent}{$slave} ne $name) { error(_g("alternative %s can't be slave of %s: %s"), $slave, $name, ($ALL{parent}{$slave} eq $slave) ? _g("it is a master alternative.") : sprintf(_g("it is a slave of %s"), $ALL{parent}{$slave}) ); } if (exists $ALL{links}{$link} and $ALL{links}{$link} ne $slave) { error(_g("alternative link %s is already managed by %s."), $link, $ALL{parent}{$ALL{links}{$link}}); } error(_g("alternative link is not absolute as it should be: %s"), $link) unless $link =~ m|^/|; error(_g("alternative path is not absolute as it should be: %s"), $file) unless $file =~ m|^/|; error(_g("alternative name (%s) must not contain '/' and spaces."), $slave) if $slave =~ m|[/\s]|; } } # Handle actions if ($action eq 'all') { config_all(); exit 0; } elsif ($action eq 'get-selections') { foreach my $alt_name (sort keys %{$ALL{objects}}) { my $obj = $ALL{objects}{$alt_name}; printf "%-30s %-8s %s\n", $alt_name, $obj->status(), $obj->current() || ""; } exit 0; } elsif ($action eq 'set-selections') { log_msg("run with @COPY_ARGV"); my $line; my $prefix = "[$progname --set-selections] "; while (defined($line = )) { chomp($line); my ($alt_name, $status, $choice) = split(/\s+/, $line, 3); if (exists $ALL{objects}{$alt_name}) { my $obj = $ALL{objects}{$alt_name}; if ($status eq "auto") { pr($prefix . _g("Call %s."), "$0 --auto $alt_name"); system($0, @pass_opts, "--auto", $alt_name); exit $? if $?; } else { if ($obj->has_choice($choice)) { pr($prefix . _g("Call %s."), "$0 --set $alt_name $choice"); system($0, @pass_opts, "--set", $alt_name, $choice); exit $? if $?; } else { pr($prefix . _g("Alternative %s unchanged because choice " . "%s is not available."), $alt_name, $choice); } } } else { pr($prefix . _g("Skip unknown alternative %s."), $alt_name); } } exit 0; } # Load the alternative info, stop on failure except for --install if (not $alternative->load("$admdir/" . $alternative->name()) and $action ne "install") { # FIXME: Be consistent for now with the case when we try to remove a # non-existing path from an existing link group file. if ($action eq "remove") { verbose(_g("no alternatives for %s."), $alternative->name()); exit 0; } error(_g("no alternatives for %s."), $alternative->name()); } if ($action eq 'display') { $alternative->display_user(); exit 0; } elsif ($action eq 'query') { $alternative->display_query(); exit 0; } elsif ($action eq 'list') { $alternative->display_list(); exit 0; } # Actions below might modify the system log_msg("run with @COPY_ARGV"); my $current_choice = ''; if ($alternative->has_current_link()) { $current_choice = $alternative->current(); # Detect manually modified alternative, switch to manual if (not $alternative->has_choice($current_choice)) { if (not -e $current_choice) { warning(_g("%s is dangling, it will be updated with best choice."), "$altdir/" . $alternative->name()); $alternative->set_status('auto'); } elsif ($alternative->status() ne "manual") { warning(_g("%s has been changed (manually or by a script). " . "Switching to manual updates only."), "$altdir/" . $alternative->name()); $alternative->set_status('manual'); } } } else { # Lack of alternative link => automatic mode verbose(_g("setting up automatic selection of %s."), $alternative->name()); $alternative->set_status('auto'); } my $new_choice; if ($action eq 'set') { $alternative->set_status('manual'); $new_choice = $path; } elsif ($action eq 'auto') { $alternative->set_status('auto'); $new_choice = $alternative->best(); } elsif ($action eq 'config') { if (not scalar($alternative->choices())) { pr(_g("There is no program which provides %s."), $alternative->name()); pr(_g("Nothing to configure.")); } elsif ($skip_auto && $alternative->status() eq 'auto') { $alternative->display_user(); } elsif (scalar($alternative->choices()) == 1 and $alternative->status() eq 'auto' and $alternative->has_current_link()) { pr(_g("There is only one alternative in link group %s: %s"), $alternative->name(), $alternative->current()); pr(_g("Nothing to configure.")); } else { $new_choice = $alternative->select_choice(); } } elsif ($action eq 'remove') { if ($alternative->has_choice($path)) { $alternative->remove_choice($path); } else { verbose(_g("alternative %s for %s not registered, not removing."), $path, $alternative->name()); } if ($current_choice eq $path) { # Current choice is removed if ($alternative->status() eq "manual") { # And it was manual, switch to auto info(_g("removing manually selected alternative - " . "switching %s to auto mode"), $alternative->name()); $alternative->set_status('auto'); } $new_choice = $alternative->best(); } } elsif ($action eq 'remove-all') { foreach my $choice ($alternative->choices()) { $alternative->remove_choice($choice); } } elsif ($action eq 'install') { if (defined($alternative->link())) { # Alternative already exists, check if anything got updated my ($old, $new) = ($alternative->link(), $inst_alt->link()); $alternative->set_link($new); if ($old ne $new and -l $old) { info(_g("renaming %s link from %s to %s."), $inst_alt->name(), $old, $new); checked_mv($old, $new); } # Check if new slaves have been added, or existing ones renamed foreach my $slave ($inst_alt->slaves()) { $new = $inst_alt->slave_link($slave); if (not $alternative->has_slave($slave)) { $alternative->add_slave($slave, $new); next; } $old = $alternative->slave_link($slave); $alternative->add_slave($slave, $new); my $new_file = ($current_choice eq $fileset->master()) ? $fileset->slave($slave) : readlink("$admdir/$slave") || ""; if ($old ne $new and -l $old) { if (-e $new_file) { info(_g("renaming %s slave link from %s to %s."), $slave,$old, $new); checked_mv($old, $new); } else { checked_rm($old); } } } } else { # Alternative doesn't exist, create from parameters $alternative = $inst_alt; } $alternative->add_choice($fileset); if ($alternative->status() eq "auto") { # Update automatic choice if needed $new_choice = $alternative->best(); } else { verbose(_g("automatic updates of %s are disabled, leaving it alone."), "$altdir/" . $alternative->name()); verbose(_g("to return to automatic updates use ". "\`update-alternatives --auto %s'."), $alternative->name()); } } # No choice left, remove everything if (not scalar($alternative->choices())) { log_msg("link group " . $alternative->name() . " fully removed"); $alternative->remove(); exit 0; } # New choice wanted if (defined($new_choice) and ($current_choice ne $new_choice)) { log_msg("link group " . $alternative->name() . " updated to point to " . $new_choice); info(_g("using %s to provide %s (%s) in %s."), $new_choice, $alternative->link(), $alternative->name(), ($alternative->status() eq "auto" ? _g("auto mode") : _g("manual mode"))); $alternative->prepare_install($new_choice); } elsif ($alternative->is_broken()) { log_msg("auto-repair link group " . $alternative->name()); warning(_g("forcing reinstallation of alternative %s " . "because link group %s is broken."), $current_choice, $alternative->name()); $alternative->prepare_install($current_choice) if $current_choice; } # Save administrative file if needed if ($alternative->is_modified()) { $alternative->save("$admdir/" . $alternative->name() . ".dpkg-tmp"); checked_mv("$admdir/" . $alternative->name() . ".dpkg-tmp", "$admdir/" . $alternative->name()); } # Replace all symlinks in one pass $alternative->commit(); exit 0; ### FUNCTIONS #### sub version { printf _g("Debian %s version %s.\n"), $progname, $version; printf _g(" Copyright (C) 1995 Ian Jackson. Copyright (C) 2000-2002 Wichert Akkerman. Copyright (C) 2009 Raphael Hertzog."); printf _g(" This is free software; see the GNU General Public Licence version 2 or later for copying conditions. There is NO warranty. "); } sub usage { printf _g( "Usage: %s [