update-alternatives class

Some commands are available from multiple sources. As an example we have /bin/sh available from busybox and from bash. The busybox version is better from a size perspective, but limited in functionality, while the bash version is much larger but also provides far more features. The alternatives system is designed to handle the situation where two commands are provided by two, or more, packages. It ensures that one of the alternatives is always the currently selected one and ensures that there are no problems with installing and/or removing the various alternatives.

The update-alternatives class is used to register a command provided by a package that may have an alternative implementation in a some other package.

In the following sections we'll use the /bin/ping command as an example. This command is available as a basic version from busybox and as a more advanced version from iputils.

Naming of the alternative commands

When supplying alternative commands the target command itself is not installed directly by any of the available alternatives. This is to ensure that no package will replace files that were installed by one of the other available alternative packages. The alternatives system will create a symlink for the target command that points to the required alternative.

For the /bin/ping case this means that neither busybox nor iputils should actually install a command called /bin/ping. Instead we see that the iputils recipe installs it's version of ping as /bin/ping.iputils:

do_install () {
    install -m 0755 -d ${D}${base_bindir} ${D}${bindir} ${D}${mandir}/man8
    # SUID root programs
    install -m 4755 ping ${D}${base_bindir}/ping.${PN}
    ...
}

If you were to look at the busybox recipe you would see that it also doesn't install a command called /bin/ping, instead it installs it's command as /bin/busybox.

The important point to note is that neither package is installing an actual /bin/ping target command.

How alternatives work

Before proceeding lets take a look at how alternatives are handled. If we have a base image that includes only busybox then look at /bin/ping we see that it is a symlink to busybox:

root@titan:/etc# ls -l /bin/ping
lrwxrwxrwx    1 root     root            7 May  3  2006 /bin/ping -> busybox

This is what is expected since the busybox version of ping is the only one installed on the system. Note again that it is only a symlink and not an actual command.

If the iputils version of ping is now installed and we look at the /bin/ping command again we see that it has been changed to a symlink pointing at the iputils version of ping - /bin/ping.iptils:

root@titan:/etc# ipkg install iputils-ping
Installing iputils-ping (20020927-r2) to root...
Downloading http://nynaeve/ipkg-titan-glibc//iputils-ping_20020927-r2_sh4.ipk
Configuring iputils-ping
update-alternatives: Linking //bin/ping to ping.iputils
root@titan:/etc# ls -l /bin/ping
lrwxrwxrwx    1 root     root           12 May 13  2006 /bin/ping -> ping.iputils

The iputils version is considered to be the more fully featured version of ping and is therefore the default when both versions are installed.

What happens if the iputils-ping package is removed now? The symlink should be changed to point back at the busybox version:

root@titan:/etc# ipkg remove iputils-ping
Removing package iputils-ping from root...
update-alternatives: Linking //bin/ping to busybox
root@titan:/etc# ls -l /bin/ping
lrwxrwxrwx    1 root     root            7 May 13  2006 /bin/ping -> busybox

This simple example shows that the alternatives system is taking care of ensuring the symlink is pointing to the correct version of the command without any special interaction from the end users.

The update-alternatives command

Available alternatives need to be registered with the alternatives system. This is handled by the update-alternatives command. The help from the command shows it's usage options:

root@titan:/etc# update-alternatives --help
update-alternatives: help:

Usage: update-alternatives --install <link> <name> <path> <priority>
       update-alternatives --remove <name> <path>
       update-alternatives --help
<link> is the link pointing to the provided path (ie. /usr/bin/foo).
<name> is the name in /usr/lib/ipkg/alternatives/alternatives (ie. foo)
<path> is the name referred to (ie. /usr/bin/foo-extra-spiffy)
<priority> is an integer; options with higher numbers are chosen.

During postinst the update-alternatives command needs to be called with the install option and during postrm it needs to be called with the remove option.

The iputils recipe actual codes this directly (rather than using the class) so we can see an example of the command being called:

pkg_postinst_${PN}-ping () {
    update-alternatives --install ${base_bindir}/ping ping ping.${PN} 100
}
pkg_prerm_${PN}-ping () {
    update-alternatives --remove ping ping.${PN}
}

In both cases the name that the alternatives are registered against, "ping", is passed in and the path to the iputils version of the command, "ping.${PN}". For the install case the actual command name (where the symlink will be made from) and a priority value are also supplied.

Priority of the alternatives

So why did the alternatives system prefer the iputils version of ping over the busybox version? It's because of the relative priorities of the available alternatives. When iputils calls update-alternatives the last parameter passed is a priority:

 update-alternatives --install ${base_bindir}/ping ping ping.${PN} 100

So iputils is specifying a priority of 100 and if you look at busybox you'll see it specifies a priority of 50 for ping. The alternative with the highest priority value is the one that update-alternatives will select as the version to actual use. In this particular situation the authors have selected a higher priority for iputils since it is the more capable version of ping and would not normally be installed unless explicitly requested.

Tracking of the installed alternatives

You can actually see which alternatives are available and what their priority is on a target system. Here we have an example in which both busybox and iptuils-ping packages are installed:

root@titan:/etc# cat /usr/lib/ipkg/alternatives/ping
/bin/ping
busybox 50
ping.iputils 100

If we remove iputils-ping, then we see that alternatives file is updated to reflect this:

root@titan:/etc# cat /usr/lib/ipkg/alternatives/ping
/bin/ping
busybox 50
root@titan:/etc#

The file lists the command first, and then each of the available alternatives and their relative priorities.

Using the update-alternatives class

Neither busybox nor iputils actually use the update-alternatives class - they call the update-alternatives functions directly. They need to call the command directly since they need to register multiple alternatives and the class does not support this. The class can only be used when you have only a single alternative to register.

To use the class you need to inherent update-alternatives and then define the name, path, link and priority as show in the following example from the jamvm recipe:

inherit autotools update-alternatives

ALTERNATIVE_NAME = "java"
ALTERNATIVE_PATH = "${bindir}/jamvm"
ALTERNATIVE_LINK = "${bindir}/java"
ALTERNATIVE_PRIORITY = "10"

where the variables to be specified are:

ALTERNATIVE_NAME [Required]

The name that the alternative is registered against and needs to be the same for all alternatives registering this command.

ALTERNATIVE_PATH [Required]

The path of the installed alternative. (This was iputils.ping in the example used previously).

ALTERNATIVE_LINK [Optional]

The name of the actual command. This is what the symlink will be called and is the actual command that the use runs. The default value is: "${bindir}/${ALTERNATIVE_NAME}"

ALTERNATIVE_PRIORITY [Optional]

The priority of this alternative. The alternative with the highest valued priority will be selected as the default. The default value is: "10".

The actual postinst and postrm commands that are registered by the class are:

update_alternatives_postinst() {
    update-alternatives --install ${ALTERNATIVE_LINK} ${ALTERNATIVE_NAME} ${ALTERNATIVE_PATH} ${ALTERNATIVE_PRIORITY}
}

update_alternatives_postrm() {
    update-alternatives --remove ${ALTERNATIVE_NAME} ${ALTERNATIVE_PATH}
}