#! /usr/bin/perl --

#use POSIX; &ENOENT;
sub ENOENT { 2; }
# Sorry about this, but the errno-part of POSIX.pm isn't in perl-*-base

# Global variables:
#  $alink            Alternative we are managing (ie the symlink we're making/removing) (install only)
#  $name             Name of the alternative (the symlink) we are processing
#  $apath            Path of alternative we are offering            
#  $apriority        Priority of link (only when we are installing an alternative)
#  $mode             action to perform (display / install / remove / remove-all / display / auto /
#                                       list / config / set)
#  $manual           update-mode for alternative (manual / auto)
#  $state            State of alternative:
#                       expected: active alternative is in database
#                       expected-inprogress: busy selecting alternative with highest priority
#                       selected: alternative is being changed by the user
#                       unexpected: active alternative not in database / error during readlink
#                       nonexistent: alternative-symlink or alternative does not exist
#  $link             Link we are working with
#  @slavenames       List with names of slavelinks
#  %slavenum         Map from name of slavelink to slave-index (into @slavelinks)
#  @slavelinks       List of slavelinks (indexed by slave-index)
#  %versionnum       Map from currently available versions into @versions and @priorities
#  @versions         List of available versions for alternative
#  %priorities       Map from @version-index to priority
#  %slavepath        Map from (@version-index,slavename) to slave-path

$version= qq(1.9.0); # This line modified by Makefile
sub usageversion {
    print(STDERR <<END)
Mandriva update-alternatives $version, derived from the Debian GNU/Linux
update-alternatives tool, which is Copyright (C) 1995 Ian Jackson. This is
free software; see the GNU General Public Licence version 2 or later for
copying conditions.  There is NO warranty.

Usage: $0 [<option> ...] <command>

Commands:
  --install <link> <name> <path> <priority>
    [--slave <link> <name> <path>] ...
                           add a group of alternatives to the system.
  --remove <name> <path>   remove <path> from the <name> group alternative.
  --remove-all <name>      remove <name> group from the alternatives system.
  --auto <name>            switch the master link <name> to automatic mode.
  --display <name>         display information about the <name> group.
  --list <name>            display all targets of the <name> group.
  --config <name>          show alternatives for the <name> group and ask the
                           user to select which one to use.
  --set <name> <path>      set <path> as alternative for <name>.

<link> is the symlink pointing to /etc/alternatives/<name>.
<name> is the master name for this link group.
<path> is the location of one of the alternative target files.
<priority> is an integer; options with higher numbers have higher priority in
  automatic mode.

Options:
  --altdir <directory>     change the alternatives directory.
  --admindir <directory>   change the administrative directory.
  --verbose                verbose operation, more output.
  --quiet                  quiet operation, minimal output.
  --help                   show this help message.
  --version                show the version.
END
        || &quit("failed to write usage: $!");
}
sub quit { print STDERR "update-alternatives: @_\n"; exit(2); }
sub badusage { print STDERR "update-alternatives: @_\n\n"; &usageversion; exit(2); }

$altdir= '/etc/alternatives';
$admindir= '/var/lib/rpm/alternatives';
$verbosemode= 0;
$mode='';
$manual= 'auto';
$|=1;

sub checkmanymodes {
    return unless $mode;
    &badusage("two modes specified: $_ and --$mode");
}

while (@ARGV) {
    $_= shift(@ARGV);
    last if m/^--$/;
    if (!m/^--/) {
        &quit("unknown argument \`$_'");
    } elsif (m/^--(help|version)$/) {
        &usageversion; exit(0);
    } elsif (m/^--verbose$/) {
        $verbosemode= +1;
    } elsif (m/^--quiet$/) {
        $verbosemode= -1;
    } elsif (m/^--install$/) {
        &checkmanymodes;
        @ARGV >= 4 || &badusage("--install needs <link> <name> <path> <priority>");
        ($alink,$name,$apath,$apriority,@ARGV) = @ARGV;
        $apriority =~ m/^[-+]?\d+/ || &badusage("priority must be an integer");
        $mode= 'install';
    } elsif (m/^--(remove|set)$/) {
        &checkmanymodes;
        @ARGV >= 2 || &badusage("--$1 needs <name> <path>");
        ($name,$apath,@ARGV) = @ARGV;
        $mode= $1;
    } elsif (m/^--(display|auto|config|list|remove-all)$/) {
        &checkmanymodes;
        @ARGV || &badusage("--$1 needs <name>");
        $mode= $1;
        $name= shift(@ARGV);
    } elsif (m/^--slave$/) {
        @ARGV >= 3 || &badusage("--slave needs <link> <name> <path>");
        ($slink,$sname,$spath,@ARGV) = @ARGV;
        defined($aslavelink{$sname}) && &badusage("slave name $sname duplicated");
        $aslavelinkcount{$slink}++ && &badusage("slave link $slink duplicated");
        $aslavelink{$sname}= $slink;
        $aslavepath{$sname}= $spath;
    } elsif (m/^--altdir$/) {
        @ARGV || &badusage("--altdir needs a <directory> argument");
        $altdir= shift(@ARGV);
    } elsif (m/^--admindir$/) {
        @ARGV || &badusage("--admindir needs a <directory> argument");
        $admindir= shift(@ARGV);
    } else {
        &badusage("unknown option \`$_'");
    }
}

defined($aslavelink{$name}) && &badusage("name $name is both primary and slave");
$aslavelinkcount{$alink} && &badusage("link $link is both primary and slave");

$mode || &badusage("need --display, --config, --set, --install, --remove, --remove-all, --list or --auto");
$mode eq 'install' || !%slavelink || &badusage("--slave only allowed with --install");

if (open(AF,"$admindir/$name")) {
    $manual= &gl("manflag");
    $manual eq 'auto' || $manual eq 'manual' || &badfmt("manflag");
    $link= &gl("link");
    while (($sname= &gl("sname")) ne '') {
        push(@slavenames,$sname);
        defined($slavenum{$sname}) && &badfmt("duplicate slave $tsname");
        $slavenum{$sname}= $#slavenames;
        $slink= &gl("slink");
        $slink eq $link && &badfmt("slave link same as main link $link");
        $slavelinkcount{$slink}++ && &badfmt("duplicate slave link $slink");
        push(@slavelinks,$slink);
    }
    while (($version= &gl("version")) ne '') {
        defined($versionnum{$version}) && &badfmt("duplicate path $tver");
       if ( -r $version || $mode eq 'remove' && $apath eq $version ) {
           push(@versions,$version);
           $versionnum{$version}= $i= $#versions;
           $priority= &gl("priority");
           $priority =~ m/^[-+]?\d+$/ || &badfmt("priority $version $priority");
           $priorities[$i]= $priority;
           for ($j=0; $j<=$#slavenames; $j++) {
               $slavepath{$i,$j}= &gl("spath");
           }
       } else {
           # File not found - remove
           &pr("Alternative for $name points to $version - which wasn't found.  Removing from list of alternatives.")
             if $verbosemode > 0;
           &gl("priority");
           for ($j=0; $j<=$#slavenames; $j++) {
               &gl("spath");
           }
       }
    }
    close(AF);
    $dataread=1;
} elsif ($! != &ENOENT) {
    &quit("failed to open $admindir/$name: $!");
}

if ($mode eq 'display') {
    if (!$dataread) {
        &pr("No alternatives for $name.");
    } else {
        &pr("$name - status is $manual.");
        if (defined($linkname= readlink("$altdir/$name"))) {
            &pr(" link currently points to $linkname");
        } elsif ($! == &ENOENT) {
            &pr(" link currently absent");
        } else {
            &pr(" link unreadable - $!");
        }
        $best= '';
        for ($i=0; $i<=$#versions; $i++) {
            if ($best eq '' || $priorities[$i] > $bestpri) {
                $best= $versions[$i]; $bestpri= $priorities[$i];
            }
            &pr("$versions[$i] - priority $priorities[$i]");
            for ($j=0; $j<=$#slavenames; $j++) {
                next unless length($tspath= $slavepath{$i,$j});
                &pr(" slave $slavenames[$j]: $tspath");
            }
        }
        if ($best eq '') {
            &pr("No versions available.");
        } else {
            &pr("Current \`best' version is $best.");
        }
    }
    exit 0;
}

if ($mode eq 'list') {
    if ($dataread) {
        for ($i = 0; $i<=$#versions; $i++) {
             &pr("$versions[$i]");
        }
    }
    exit 0;
}

$best= '';
for ($i=0; $i<=$#versions; $i++) {
    if ($best eq '' || $priorities[$i] > $bestpri) {
        $best= $versions[$i]; $bestpri= $priorities[$i];
    }
}
$current= $best;

if ($mode eq 'config') {
    if (!$dataread) {
	&pr("No alternatives for $name.");
    } else {
	&config_alternatives($name);
    }
} elsif ($mode eq 'set') {
    if (!$dataread) {
	&pr("No alternatives for $name.");
    } else {
	&set_alternatives();
    }
} elsif (defined($linkname= readlink("$altdir/$name"))) {
    if (defined($versionnum{$linkname})) {
        $state= 'expected';
        $current= $linkname;
    } elsif (defined($linkname2= readlink("$altdir/$name.rpm-tmp"))) {
        $state= 'expected-inprogress';
    } elsif ( -r "$altdir/$name") {
        $state= 'unexpected';
    } else {
        &pr("$linkname has been deleted, returning to automatic selection.")
          if $verbosemode > 0 && $manual eq 'manual';
        $state= 'nonexistent';
        $manual= 'auto';
    }
} elsif ($! == &ENOENT) {
    $state= 'nonexistent';
} else {
    $state= 'unexpected';
}

# Possible values for:
#   $manual      manual, auto
#   $state       expected, expected-inprogress, unexpected, nonexistent
#   $mode        auto, install, remove, remove-all, config, set
# all independent

if ($mode eq 'auto') {
    &pr("Setting up automatic selection of $name.")
      if $verbosemode > 0;
    unlink("$altdir/$name.rpm-tmp") || $! == &ENOENT ||
        &quit("unable to remove $altdir/$name.rpm-tmp: $!");
    unlink("$altdir/$name") || $! == &ENOENT ||
        &quit("unable to remove $altdir/$name.rpm-tmp: $!");
    $state= 'nonexistent';
    $manual= 'auto';
} elsif ($state eq 'nonexistent') {
    if ($manual eq 'manual') {
        &pr("$altdir/$name has been deleted, returning to automatic selection.")
          if $verbosemode > 0;
        $manual= 'auto';
    }
}

#   $manual      manual, auto
#   $state       expected, expected-inprogress, unexpected, nonexistent
#   $mode        auto, install, remove, remove-all, config, set
# mode=auto <=> state=nonexistent

if ($state eq 'unexpected' && $manual eq 'auto') {
    &pr("$altdir/$name has been changed (manually or by a script).\n".
        "Switching to manual updates only.")
      if $verbosemode > 0;
    $manual= 'manual';
}

#   $manual      manual, auto
#   $state       expected, expected-inprogress, unexpected, nonexistent
#   $mode        auto, install, remove, remove-all, config, set
# mode=auto <=> state=nonexistent
# state=unexpected => manual=manual

&pr("Checking available versions of $name, updating links in $altdir ...\n".
    "(You may modify the symlinks there yourself if desired - see \`man ln'.)")
  if $verbosemode > 0;

if ($mode eq 'install') {
    if ($link ne $alink && $link ne '') {
        &pr("Renaming $name link from $link to $alink.")
          if $verbosemode > 0;
        rename_mv($link,$alink) || $! == &ENOENT ||
            &quit("unable to rename $link to $alink: $!");
    }
    $link= $alink;
    if (!defined($i= $versionnum{$apath})) {
        push(@versions,$apath);
        $versionnum{$apath}= $i= $#versions;
    }
    $priorities[$i]= $apriority;
    for $sname (keys %aslavelink) {
        if (!defined($j= $slavenum{$sname})) {
            push(@slavenames,$sname);
            $slavenum{$sname}= $j= $#slavenames;
        }
        $oldslavelink= $slavelinks[$j];
        $newslavelink= $aslavelink{$sname};
        $slavelinkcount{$oldslavelink}-- if $oldslavelink ne '';
        $slavelinkcount{$newslavelink}++ &&
            &quit("slave link name $newslavelink duplicated");
        if ($newslavelink ne $oldslavelink && $oldslavelink ne '') {
            &pr("Renaming $sname slave link from $oldslavelink to $newslavelink.")
              if $verbosemode > 0;
            rename_mv($oldslavelink,$newslavelink) || $! == &ENOENT ||
                &quit("unable to rename $oldslavelink to $newslavelink: $!");
        }
        $slavelinks[$j]= $newslavelink;
    }
    for ($j=0; $j<=$#slavenames; $j++) {
        $slavepath{$i,$j}= $aslavepath{$slavenames[$j]};
    }
}

if ($mode eq 'remove') {
    if ($manual eq "manual" && $state eq "expected" && $apath eq $current) {
    	&pr("Removing manually selected alternative - switching to auto mode");
	$manual= "auto";
    }
    if (defined($i= $versionnum{$apath})) {
        $k= $#versions;
        $versionnum{$versions[$k]}= $i;
        delete $versionnum{$versions[$i]};
        $versions[$i]= $versions[$k]; $#versions--;
        $priorities[$i]= $priorities[$k]; $#priorities--;
        for ($j=0; $j<=$#slavenames; $j++) {
            $slavepath{$i,$j}= $slavepath{$k,$j};
            delete $slavepath{$k,$j};
        }
    } else {
        &pr("Alternative $apath for $name not registered, not removing.")
          if $verbosemode > 0;
    }
}

if ($mode eq 'remove-all') {
    $manual = "auto";
    my $k = $#versions;
    for (my $i = 0; $i <= $#versions; $i++) {
        $k--;
        delete $versionnum{$versions[$i]};
        $#priorities--;
        for (my $j = 0; $j <= $#slavenames; $j++) {
            $slavepath{$i,$j}= $slavepath{$k,$j};
            delete $slavepath{$k,$j};
        }
    }
    $#versions=$k;
}

for ($j=0; $j<=$#slavenames; $j++) {
    for ($i=0; $i<=$#versions; $i++) {
        last if $slavepath{$i,$j} ne '';
    }
    if ($i > $#versions) {
        &pr("Discarding obsolete slave link $slavenames[$j] ($slavelinks[$j]).")
          if $verbosemode > 0;
        unlink("$altdir/$slavenames[$j]") || $! == &ENOENT ||
            &quit("unable to remove $slavenames[$j]: $!");
        safe_rm_symlink("$altdir/$slavenames[$j]", $slavelinks[$j]);
        $k= $#slavenames;
        $slavenum{$slavenames[$k]}= $j;
        delete $slavenum{$slavenames[$j]};
        $slavelinkcount{$slavelinks[$j]}--;
        $slavenames[$j]= $slavenames[$k]; $#slavenames--;
        $slavelinks[$j]= $slavelinks[$k]; $#slavelinks--;
        for ($i=0; $i<=$#versions; $i++) {
            $slavepath{$i,$j}= $slavepath{$i,$k};
            delete $slavepath{$i,$k};
        }
        $j--;
    }
}
        
if ($manual eq 'manual') {
    &pr("Automatic updates of $altdir/$name are disabled.")
      if $verbosemode > 0;
    &pr("To return to automatic updates use \`update-alternatives --auto $name'.")
      if $verbosemode > 0;
} else {
    if ($state eq 'expected-inprogress') {
        &pr("Recovering from previous failed update of $name ...");
        rename_mv("$altdir/$name.rpm-tmp","$altdir/$name") ||
            &quit("unable to rename $altdir/$name.rpm-tmp to $altdir/$name: $!");
        $state= 'expected';
    }
}

#   $manual      manual, auto
#   $state       expected, expected-inprogress, unexpected, nonexistent
#   $mode        auto, install, remove, remove-all, config, set
# mode=auto <=> state=nonexistent
# state=unexpected => manual=manual
# manual=auto => state!=expected-inprogress && state!=unexpected

open(AF,">$admindir/$name.rpm-new") ||
    &quit("unable to open $admindir/$name.rpm-new for write: $!");
&paf($manual);
&paf($link);
for ($j=0; $j<=$#slavenames; $j++) {
    &paf($slavenames[$j]);
    &paf($slavelinks[$j]);
}
&paf('');
$best= '';
$currentnum= -1;
for ($i=0; $i<=$#versions; $i++) {
    if ($best eq '' || $priorities[$i] > $bestpri) {
        $best= $versions[$i]; $bestpri= $priorities[$i]; $bestnum= $i;
    }
    &paf($versions[$i]);
    &paf($priorities[$i]);
    for ($j=0; $j<=$#slavenames; $j++) {
        &paf($slavepath{$i,$j});
    }
    $currentnum = $i if ($versions[$i] eq $current);
}
&paf('');
close(AF) || &quit("unable to close $admindir/$name.rpm-new: $!");

if ($manual eq 'auto') {
    $current= $best;
    $currentnum= $bestnum;
}

# We want to have the links checked and updated if we are in automatic mode or
# the link currently points to a known manual alternative or the user just
# changed the link destination using --set or --config.
if ($manual eq 'auto' || $state eq 'expected' || $state eq 'selected') {
    if ($best eq '') {
        &pr("Last package providing $name ($link) removed, deleting it.")
          if $verbosemode > 0;
        unlink("$altdir/$name") || $! == &ENOENT ||
            &quit("unable to remove $altdir/$name: $!");
        safe_rm_symlink("$altdir/$name", "$link");
        unlink("$admindir/$name.rpm-new") ||
            &quit("unable to remove $admindir/$name.rpm-new: $!");
        unlink("$admindir/$name") || $! == &ENOENT ||
            &quit("unable to remove $admindir/$name: $!");
        exit(0);
    } else {
        &quit("bug: active alternative disappeared, please report") if ($currentnum eq -1);
        ensure_symlink("$altdir/$name",$link);
        if (defined($linkname= readlink("$altdir/$name")) && $linkname eq $current) {
            &pr("Leaving $name ($link) pointing to $current.")
              if $verbosemode > 0;
        } else {
            &pr("Updating $name ($link) to point to $current.")
              if $verbosemode > 0;
        }
        unlink("$altdir/$name.rpm-tmp") || $! == &ENOENT ||
            &quit("unable to ensure $altdir/$name.rpm-tmp nonexistent: $!");
        checked_symlink($current,"$altdir/$name.rpm-tmp");
    }
}

rename_mv("$admindir/$name.rpm-new","$admindir/$name") ||
    &quit("unable to rename $admindir/$name.rpm-new to $admindir/$name: $!");

if ($manual eq 'auto' || $state eq 'expected' || $state eq 'selected') {
    rename_mv("$altdir/$name.rpm-tmp","$altdir/$name") ||
        &quit("unable to install $altdir/$name.rpm-tmp as $altdir/$name");
    for ($j=0; $j<=$#slavenames; $j++) {
        $sname= $slavenames[$j];
        $slink= $slavelinks[$j];
        $spath= $slavepath{$currentnum,$j};
        unlink("$altdir/$sname.rpm-tmp") || $! == &ENOENT ||
            &quit("unable to ensure $altdir/$sname.rpm-tmp nonexistent: $!");
        if ($spath eq '') {
            &pr("Removing $sname ($slink), not appropriate with $current.")
              if $verbosemode > 0;
            unlink("$altdir/$sname") || $! == &ENOENT ||
                &quit("unable to remove $altdir/$sname: $!");
	    safe_rm_symlink("$altdir/$sname", "$slink");
        } else {
            if (defined($linkname= readlink("$altdir/$sname")) && $linkname eq $spath) {
                &pr("Leaving $sname ($slink) pointing to $spath.")
                  if $verbosemode > 0;
            } else {
                &pr("Updating $sname ($slink) to point to $spath.")
                  if $verbosemode > 0;
            }
            ensure_symlink("$altdir/$sname",$slink);
            checked_symlink("$spath","$altdir/$sname.rpm-tmp");
            checked_mv("$altdir/$sname.rpm-tmp","$altdir/$sname");
        }
    }
}

sub set_alternatives {
    $manual = "manual";
    # Get preferred number
    my $preferred = -1;
    for (my $i = 0; $i <= $#versions; $i++) {
        if($versions[$i] eq $apath) {
            $preferred = $i;
            last;
        }
    }
    if($preferred == -1) {
        &quit("Cannot find alternative `$apath'.");
    }
    print STDOUT "Using \`$apath' to provide \`$name'.\n";
    $current = $versions[$preferred];
    $state= 'selected';
}

sub config_message {
    if ($#versions == 0) {
	print "\nThere is only 1 program which provides $name\n";
	print "($versions[0]). Nothing to configure.\n";
	return;
    }
    printf(STDOUT "\nThere are %s programs which provide \`$name'.\n\n", $#versions+1);
    printf(STDOUT "  Selection    Command\n");
    printf(STDOUT "-----------------------------------------------\n");
    for ($i=0; $i<=$#versions; $i++) {
	printf(STDOUT "%s%s    %s        %s\n", 
	    (readlink("$altdir/$name") eq $versions[$i]) ? '*' : ' ',
	    ($best eq $versions[$i]) ? '+' : ' ',
	    $i+1, $versions[$i]);
    }
    printf(STDOUT "\nEnter to keep the default[*], or type selection number: ");
}

sub config_alternatives {
    do {
	&config_message;
	if ($#versions == 0) { return; }
	$preferred=<STDIN>;
	chop($preferred);
    } until $preferred eq '' || $preferred>=1 && $preferred<=$#versions+1 &&
	($preferred =~ m/[0-9]*/);
    if ($preferred ne '') {
    	$manual = "manual";
	$preferred--;
	print STDOUT "Using \`$versions[$preferred]' to provide \`$name'.\n";
        $current = $versions[$preferred];
        $state= 'selected';
    }
}

sub pr { print(STDOUT "@_\n") || &quit("error writing stdout: $!"); }
sub paf {
    $_[0] =~ m/\n/ && &quit("newlines prohibited in update-alternatives files ($_[0])");
    print(AF "$_[0]\n") || &quit("error writing stdout: $!");
}
sub gl {
    $!=0; $_= <AF>;
    length($_) || &quit("error or eof reading $admindir/$name for $_[0] ($!)");
    s/\n$// || &badfmt("missing newline after $_[0]");
    $_;
}
sub badfmt {
    &quit("internal error: $admindir/$name corrupt: $_[0]");
}
sub rename_mv {
    return (rename($_[0], $_[1]) || (system(("mv", $_[0], $_[1])) == 0));
}
sub checked_symlink {
    my ($filename, $linkname) = @_;
    my_symlink($filename, $linkname) ||
	&quit("unable to make $linkname a symlink to $filename: $!");
}

sub my_symlink {
	my ($filename, $linkname) = @_;
	local $_ = $linkname;
	s|//|/|g;
	my $relative = join('/', ("..")  x (split(m|/|) - 2));
#	symlink($relative.$filename,$linkname) ||
	symlink($filename,$linkname) ||
	&quit("unable to make $linkname a symlink to $relative.$filename: $!");

}

sub checked_mv {
    my ($source, $dest) = @_;
    rename_mv($source, $dest) ||
	&quit("unable to install $source as $dest: $!");
}

sub ensure_symlink {
    my ($linkdest, $symlink) = @_;
    if (!defined($linkname= readlink($symlink)) && $state ne 'selected' && $! != &ENOENT) {
        &pr("warning: $symlink is supposed to be a symlink to\n".
            " $linkdest, or nonexistent; however, readlink failed: $!")
          if $verbosemode > 0;
    } elsif ($linkname ne $linkdest) {
        if ($state eq 'selected' || ! -r $symlink) {
            unlink("$symlink.rpm-tmp") || $! == &ENOENT ||
                &quit("unable to ensure $symlink.rpm-tmp nonexistent: $!");
            my_symlink($linkdest,"$symlink.rpm-tmp") ||
                &quit("unable to make $symlink.rpm-tmp a symlink to $linkdest: $!");
            rename_mv("$symlink.rpm-tmp",$symlink) ||
            &quit("unable to install $symlink.rpm-tmp as $symlink: $!");
        } else {
            &pr("Not modifying symlink $symlink to point to $linkdest\n".
            "as it is a valid symlink pointing to $linkname\n".
            "not managed by alternatives system.")
              if $verbosemode > 0;
        }
    }
}

sub safe_rm_symlink {
    my ($target, $link) = @_;
    if (defined($realtarget = readlink($link)) && $realtarget ne $target && -r $link) {
        &pr("Not removing $link as it is a valid symlink pointing to\n".
            "$realtarget not managed by alternatives system.")
          if $verbosemode > 0;
    } elsif (! -l $link && -r $link) {
        &pr("Not removing $link as it is not a symlink.")
          if $verbosemode > 0;
    } else {
        unlink($link) || $! == &ENOENT ||
          &quit("unable to remove $link: $!");
    }
}

exit(0);

# vim: nowrap ts=8 sw=4
