#!/usr/bin/perl
#
# update-alternatives
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
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 [