Create a module for Gentoo Eselect

I do Love eselect!

gentoo-eselectFor those who don't know eselect is a 'modular administration and configuration framework'. In simpler words it is a management tool shipped with Gentoo, able to switch between packages versions and configurations files, working with symbolic links and environmental variables.

Just to better clarify eselect job, it is able to make you switch between Java VM versions, Python releases , Gcc compilers and it makes it possible to choose your favorite editor.

Eselect is a modular tool, meaning that you just need to create a simple .eselect file to make it run your own commands. What I'm going to explain below is how to create a simple module to manage a symbolic link.

Aim of the tutorial

What I want to achieve in this little tutorial is to create a simple switch to quickly change my /etc/resolv.conf file. This file tells to any network interface where to look for DNS resolving.

As during last days I had problems with DNS, I was forced to switch very often from my default domain resolver (this case an internal server) to an external one ( OpenDns ). Using eselect for this purpose really simplified my life!

Preparation : create the 'alternatives'

As we want to switch between two different configurations we need to create those alternatives. Open a terminal, cd to /etc and create the files. I created one called resolv.orig and one called resolv.opends, to be used when local DNS resolver is down.

# Going to /etc directory  
cd /etc  
# Copying original resolv.conf
# to the first alternative
cp resolv.conf resolv.orig  
# Creating new resolv.conf alternative
# using OpenDNS IPs
echo -e "nameserver 208.67.222.222\nnameserver 208.67.220.220" > resolv.prova

Now we have to delete the original file and replace it with a symlink, obviously to the .orig file

# Deleting physical file  
rm resolv.conf  
# Creating symlink
ln -s resolv.orig resolv.conf

We are done with pre-configuration, let's proceed with eselect module creation

Create the module

For a complete developer guide to eselect module creation, please refer to Gentoo Wiki official guide . This is just a 'for examples' guide for a quick start.

First of all all eselect modules should be located in your /usr/share/eselect/modules directory, so go there and create a new .eselect file

# Going into eselect modules directory  
cd /usr/share/eselect/modules  
# Creating a new module file
touch resolv.eselect

Now use your favorite editor to edit it. In this example I'm using vim

# Opening file with vim  
vim resolv.eselect

Head section


As always the head section of the file contains meta informations about the module and the author, will just quickly review it below

# -*-eselect-*-  vim: ft=eselect  
# Copyright 1999-2009 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Id: $

# This will appear when listing the available modules
DESCRIPTION="Manage the /etc/resolv.conf symlink"  
# Maintainer email
MAINTAINER="personal@andreaolivato.net"  
# Date, used to create a version
SVN_DATE='$Date: 2009-10-23 12:00:07 +0200 (Fri, 23 Oct 2009) $'  
# Creating version from date
VERSION=$(svn_date_to_version "${SVN_DATE}")

Find the alternatives


The first important function we need to create is the one taking care of listing all the possible alternatives that we have for our symlink. This means that you have to list all the files which can be linked to resolv.conf . In our case, there are only two : resolv.orig and resolv.opendns.

The easiest procedure would be to write those files directly, but as we want to learn how to deal with eselect properly let's create a bash function to list those two file. This way if in the future we would like to create a new alternative, we don't need to modify our code.

In the below function I just listed (using ls command) all the files starting with resolv. in the /etc folder and then excludes (using grep -v) the original file, which is resolv.conf. This way we can add more alternative by creating resolv.xxx files. Here's the code

find_targets() {  
    local p
    for p in $(ls /etc/resolv.* | grep -v conf )
    do
        echo $p
    done;
}

Create and remove the symlink


Proceeding with our code, we now need to be able to remove and create the symlink.

To remove it, we just need to use the rm command like this

remove_symlink() {  
    rm "/etc/resolv.conf"
}

To create the symlink, we need to deal with the user choice. This choice is represented by a number, identifying the file to switch the link to. This means that we need to associate a parameter passed to the function with the position of the choice in our file list.

set_symlink() {  
    # Get the parameter
    local target=${1}
    # Check it is a number
    if is_number "${target}" ; then
        # Get the list of the files via the previously created function
        local targets=( $(find_targets) )
        # Get the file associated with the parameter passed
        target=${targets[target - 1]}
    fi

    # If the result file does not exist
    if [[ -z "$target" ]] ; then
        # We output an error
        die -q "Target "${1}" doesn't appear to be valid!"
   # While if it exists
    else
        # We create the link
        ln -s "${target}" "/etc/resolv.conf"
    fi
}

Put the things together


Now we got a function to list, one to remove and one to insert. We need to put them together and switch between them depending on the user choices. The following function receives the parameter from the user, checks it, remove the current link if it exists and the call the create function forwarding the parameter.

do_set() {

    if [[ -z ${1} ]] ; then
        # If no parameter was passed
        die -q "You didn't tell me what to set the symlink to"
    elif [[ -L /etc/resolv.conf ]] ; then
        # If the links exists try to remove it
        if ! remove_symlink ; then
            # If can't remove it output error
            die -q "Couldn't remove existing symlink"
        # Try to create
        elif ! set_symlink "${1}" ; then
            # If can't create, output error
            die -q "Couldn't set a new symlink"
        fi
    # If the link exists but is not a link but a physical file
    elif [[ -e /etc/resolv.conf ]] ; then
        # We have something strange, output error
        die -q "/etc/resolv.conf exists but is not a symlink"
    else
        # Try to create
        set_symlink "${1}" || die -q "Couldn't set a new symlink"
    fi
}

The complete file


Remaining needed functions are mostly descriptive and I'm not going to review them in detail. Below is the complete code I used for the module.

# -*-eselect-*-  vim: ft=eselect  
# Copyright 1999-2009 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Id: $

DESCRIPTION="Manage the /etc/resolv.conf symlink"  
MAINTAINER="personal@andreaolivato.net"  
SVN_DATE='$Date: 2009-09-20 22:26:07 +0200 (Sun, 20 Sep 2009) $'  
VERSION=$(svn_date_to_version "${SVN_DATE}")

find_targets() {  
    local p
    for p in $(ls /etc/resolv.* | grep -v conf )
    do
        echo $p
    done;

}

remove_symlink() {  
    rm "/etc/resolv.conf"
}

set_symlink() {  
    local target=${1}
    if is_number "${target}" ; then
        local targets=( $(find_targets) )
        target=${targets[target - 1]}
    fi

    if [[ -z "$target" ]] ; then
        die -q "Target \"${1}\" doesn't appear to be valid!"
    else
        ln -s "${target}" "/etc/resolv.conf"
    fi
}

describe_show() {  
    echo "Show the current resolv.conf symlink"
}

do_show() {  
    write_list_start "Current resolv.conf symlink:"
    if [[ -L /etc/resolv.conf ]] ; then
        local resolv=$(canonicalise "/etc/resolv.conf")
        write_kv_list_entry "${resolv%/}" ""
    else
        write_kv_list_entry "(unset)" ""
    fi
}

describe_list() {  
    echo "List available resolv.conf symlink targets"
}

do_list() {  
    local i targets=( $(find_targets) )
    write_list_start "Available resolv.conf symlink targets:"
    for (( i = 0; i < ${#targets[@]}; i++ )) ; do
        [[ ${targets[i]} = \
            $(basename "$(canonicalise "/etc/resolv.conf")") ]] \
            && targets[i]=$(highlight_marker "${targets[i]}")
    done
    write_numbered_list -m "(none found)" "${targets[@]}"
}

describe_set() {  
    echo "Set a new resolv.conf symlink target"
}

describe\_set\_parameters() {  
    echo ""
}

describe\_set\_options() {  
    echo "target : Target name or number (from 'list' action)"
}

do_set() {  
    if [[ -z ${1} ]] ; then
        die -q "You didn't tell me what to set the symlink to"
    elif [[ -L /etc/resolv.conf ]] ; then

        if ! remove_symlink ; then
            die -q "Couldn't remove existing symlink"
        elif ! set_symlink "${1}" ; then
            die -q "Couldn't set a new symlink"
        fi
    elif [[ -e /etc/resolv.conf ]] ; then
        die -q "/etc/resolv.conf exists but is not a symlink"
    else
        set_symlink "${1}" || die -q "Couldn't set a new symlink"
    fi
}

Usage

After you saved the file, you can start using the new module.

To list available files do

eselect resolv list

To set the original one

eselect resolv set 1

To set the OpenDNS one

eselect resolv set 2

To show the current choice

eselect resolv show
Andrea Olivato