Product SiteDocumentation Site

Chapter 2. Developing Listaller-ready applications

2.1. Guide to making relocatable applications
2.1.1. The problem
2.1.2. The solution
2.1.3. Hello World!
2.1.4. Initialization
2.1.5. Basic usage
2.1.6. GLib-style API
2.1.7. Useful utility functions
2.1.8. Autoconf/Automake build system integration
2.1.9. KDE integration
2.1.10. Full API reference
2.1.11. More examples
2.2. The Ligcc tools
2.2.1. What is ligcc?
2.2.2. How to use ligcc?
2.3. Relaytool
2.3.1. What is Relaytool?
2.3.2. How to use Relaytool?
We created several standalone tools, the libuild programs, that are designed to help Linux developers write better software that runs on a wider variety of distributions. Most of them are independent of Listaller itself: you can use them even if you don't provide your software using the rest of our software.

2.1. Guide to making relocatable applications

2.1.1. The problem

Listaller supports relocation. This means that a package can be installed to any location, like how Win32 installers let you choose a directory. However, most applications are not relocatable. The paths where in they search for data files are usually hardd at compile time.
On Win32, applications and libraries are easily relocatable because applications and DLLs can use GetModuleFilename() to obtain their full path.
On Linux however, no easy mechanisms exist. There is no function equivalent to GetModuleFilename(). For executables, you can still find your full location by resolving the symlink /proc/self/exe, but that won't work for libraries.

2.1.2. The solution

This is why we have developed BinReloc. BinReloc provides an easy-to-use API that uses dynamic linker and kernel magic to find out the full path of your application or library.
Highlights
  • It can be statically included in your project.
  • It's small, only about 20 KB of C source code (I suspect it's only about 10 KB if you remove all the inline documentation comments).
  • It has absolutely no dependancies other than libc.
  • It's public domain, which means you can do anything you want with the code, including relicensing it under a different license.
  • Portability to other operating systems will not be affected: BinReloc will fallback to hardcoded paths if it's running on an operating system which has no executable relocation features. You can also completely disable BinReloc with one simple macro, making your program behave exactly like when you were using hardcoded paths.
  • There are two versions of BinReloc: a plain C version, and glib version which even has a glib-style API.

Tip for KDE developers

As of April 21 2004, BinReloc-like functionality has been added to the KDE-libs, in the KStandardDirs class. If your application uses KStandardDirs to lookup data files, your application will be automatically relocatable, so using BinReloc is not necessary. Libraries however will not benefit from this, and must use BinReloc directly.

2.1.3. Hello World!

Let's begin with a BinReloc "Hello World" tutorial. We will use the plain C version of BinReloc. The glib version's API is only slightly different from the plain C API, so don't worry about the API differences. In this tutorial, our imaginary developer, Joe, will show you everything he does when writing the Hello World program.
  1. Generate BinReloc source files
    Joe downloads the BinReloc SDK from the Listaller Tools section of the download page. He extracts the archive in his home folder. A folder called binreloc-2.0 appears.
    [joe@localhost /home/joe]$ tar xzf binreloc-2.0.tar.gz
    [joe@localhost /home/joe]$ cd binreloc-2.0</div>
    
    Joe's Hello World program doesn't use glib, so he wants the plain C version of BinReloc. Joe runs the following commands to generate the BinReloc source files:
    [joe@localhost /home/joe/binreloc-2.0]$ ./generate.pl normal
    Source code written to 'binreloc.c'
    Header written to 'binreloc.h'
    [joe@localhost /home/joe/binreloc-2.0]$ mkdir ~/helloworld
    [joe@localhost /home/joe/binreloc-2.0]$ mv binreloc.c binreloc.h ~/helloworld/</div>
    
  2. Write the program
    Now that Joe has generated the BinReloc source files, he continues with writing a Hello World program:
    #include <stdio.h>
    #include "binreloc.h"
    #ifndef NULL
        #define NULL ((void *) 0)
    #endif
    
    int main () {
        BrInitError error;
    
        if (br_init (&error) == 0 && error != BR_INIT_ERROR_DISABLED) {
            printf ("Warning: BinReloc failed to initialize (error code %d)\n", error);
            printf ("Will fallback to hardcoded default path.\n");
        }
    
        printf ("The full path of this application is: %s\n", br_find_exe ("default fallback path"));
        return 0;
    }
    
    He saves this file as /home/joe/helloworld/hello.c.
  3. Compile & run
    Now it is time to compile & run the program:
    [joe@localhost /home/joe/helloworld]$ gcc -DENABLE_BINRELOC hello.c binreloc.c -o hello
    [joe@localhost /home/joe/helloworld]$ ./hello
    The full path of this application is: /home/joe/helloworld/hello
    
    Yes, it's this easy!

    How to disable BinReloc

    The -DENABLE_BINRELOC argument enables BinReloc support. BinReloc is only enabled if this macro is defined. Let's take a look at what happens if the macro is not defined:
    [joe@localhost /home/joe/helloworld]$ gcc hello.c binreloc.c -o hello
    [joe@localhost /home/joe/helloworld]$ ./hello
    The full path of this application is: default fallback path
    

2.1.4. Initialization

BinReloc must be initialize by calling one of the BinReloc initialization functions:
If you're using BinReloc in an application, then call br_init(). The definition is:
int br_init (BrInitError *error);
This function returns 1 on success, and 0 if BinReloc failed to initialize. If BinReloc failed to initialize, then the error code will be stored in error. The following error codes are available:
typedef enum {
        /* Cannot allocate memory. */
        BR_INIT_ERROR_NOMEM,
        /* Unable to open /proc/self/maps; see errno for details. */
        BR_INIT_ERROR_OPEN_MAPS,
        /* Unable to read from /proc/self/maps; see errno for details. */
        BR_INIT_ERROR_READ_MAPS,
        /* The file format of /proc/self/maps is invalid; kernel bug? */
        BR_INIT_ERROR_INVALID_MAPS,
        /* BinReloc is disabled. */
        BR_INIT_ERROR_DISABLED
} BrInitError;

If you're using BinReloc in a library, then call br_init_lib(). The definition is: int br_init_lib (BrInitError *error);
This function returns 1 on success, and 0 if BinReloc failed to initialize.
If you don't initialize BinReloc, or if initialization failed, then all BinReloc functions will return the fallback paths, so even if initialization failed, it's not fatal. Initialization will fail if BinReloc is disabled (because ENABLE_BINRELOC is not defined), or because the application is running on a platform which doesn't support relocating executables (non-Linux platforms).

2.1.5. Basic usage

There are more functions besides br_find_exe(). Here is a list of all relocation functions:
Table 2.1. Relocation functions
Function Returns
br_find_exe()
The full path of your application or library.
br_find_exe_dir()
The folder in which your application or library is located.
br_find_prefix()
The prefix in which your application or library is located. This function assumes that your binary is located inside an FHS-compatible directory structure ($prefix/bin/ or $prefix/lib/). Examples:
  • Your binary is /usr/bin/foo. It will return /usr.
  • Your library is /usr/local/lib/libfoo.so. It will return /usr/local.
  • Your binary is /Applications/CoolApp2040XP/CoolApp. It will return /Applications".
So basically, it returns dirname(executable_filename) + "/.."
br_find_bin_dir()
PREFIX + "/bin"
br_find_sbin_dir()
PREFIX + "/sbin"
br_find_data_dir()
PREFIX + "/share"
br_find_locale_dir()
PREFIX + "/locale"
br_find_lib_dir()
PREFIX + "/lib"
br_find_libexec_dir()
PREFIX + "/libexec"
br_find_etc_dir()
PREFIX + "/etc"

All functions in the above table are declared like this: char *br_find_something (const char *default_path);
default_path is used as fallback: if the BinReloc isn't initialized, or failed to initialize, then a copy of default_path will be returned. Or if the default_path is NULL, NULL will be returned.

Warning

Note that the return values of all of the above functions must be freed when no longer necessary, except if the return value is NULL.
All BinReloc functions have inline documentation! So just take a look at binreloc.c if you need more info about a certain function.

2.1.6. GLib-style API

There's also a BinReloc version with a glib-style API. Generating this version is just like generating the normal version:
[joe@localhost /home/joe/binreloc-2.0]$ ./generate.pl glib
Source code written to 'binreloc.c'
Header written to 'binreloc.h'
The API is almost identical to the plain C version, except that it uses glib-style names and glib data types, such as GError. See the full API reference.

2.1.7. Useful utility functions

The plain C version of BinReloc provides some utility functions for modifying strings and paths, because many applications will need such functionality. The GLib version doesn't contain these utility functions because GLib already has its own utility functions. Note that these utility functions are fully portable, and can be used even when BinReloc is not enabled/initialized.
char *br_strcat (const char *str1, const char *str2);
  • str1: A string.
  • str2: Another string.
  • Returns: A newly-allocated string. This string should be freed when no longer needed.
Concatenate str1 and str2 to a newly allocated string.
Example:
char *datafile;

datafile = br_strcat ("/usr", "/foo/mydata.txt");
load_data_file (datafile);
free (datafile);</pre>

2.1.8. Autoconf/Automake build system integration

Most Autoconf/Automake projects use macros that define a hardcoded path. Let's take a look at this piece of code as example.
In Makefile.am:
INCLUDES = $(LIBGLADE_CFLAGS) \
           -DDATADIR=\"$(datadir)\"

bin_PROGRAMS = foo
foo_SOURCES = main.c
In main.c:
xml = glade_xml_new (DATADIR "/foobar/glade/main.glade", NULL, NULL);
How to use BinReloc:
  1. Use the special BinReloc Autoconf Macro (binreloc.m4). This file can be found in the BinReloc SDK.
    Append the contents of binreloc.m4 to acinclude.m4 (which is in the same folder as configure.in). Create acinclude.m4 if it doesn't exist.
    In configure.in, put the command AM_BINRELOC somewhere.
    The AM_BINRELOC macro checks whether BinReloc should be enabled (whether the system supports the feature, whether the user explicitly disabled it, etc). The variable $br_cv_binreloc will be set to 'yes' if BinReloc is enabled, or 'no' otherwise.
  2. Copy binreloc.c and binreloc.h to your source code directory.
  3. Add BINRELOC_CFLAGS and binreloc.c/binreloc.h to Makefile.am:
    AM_CPPFLAGS = $(BINRELOC_CFLAGS)
    ...
    foo_SOURCES = main.c <span class="highlight">\
                  binreloc.h \
                  binreloc.c
    
  4. At the beginning of main.c, add:
    #include "binreloc.h"
    Somewhere in main.c:
    gchar *dir, *file;
    
    gbr_init (NULL);
    dir = br_find_data_dir (DEFAULT_DATA_DIR);
    file = g_strdup_printf ("%s/foobar/glade/main.glade", dir);
    g_free (dir);
    
    xml = glade_xml_new (file, NULL, NULL);
    g_free (file);
    
And that was it! Your configure script will now have a --enable-binreloc=[yes/no/auto] option. The default value for this option is --enable-binreloc=auto, which will automatically check whether BinReloc support is desired. It does so by checking for the following things:
  • Whether /proc/self/maps is available.
  • Whether the user told configure to use a different location for a specific directory, such as by passing --bindir=/foo/bin.
Users can always disable BinReloc manually by passing --disable-binreloc to the configure script.

2.1.9. KDE integration

Note to KDE developers

As of April 21 2004, BinReloc-like functionality has been added to the KDE-libs, in the KStandardDirs class. If your application uses KStandardDirs to lookup data files, your application will be automatically relocatable, so using BinReloc is not necessary. Libraries however will not benefit from this, and must use BinReloc directly.
In your program's initialization function, add the following code:
KGlobal::dirs()->addPrefix(br_find_prefix(DEFAULT_PREFIX));
Make sure you use KGlobal::dirs() to lookup data files througout your entire program. If you create new instances of KStandardDirs, you need the re-add the prefix.
If you want to use KIconLoader to load icons from whever your program is installed, add this:
KGlobal::iconLoader()->addAppDir(br_find_data_dir(DEFAULT_DATA_DIR));

2.1.10. Full API reference

... will be available very soon!

2.1.11. More examples

The contrib/binreloc/tests folder in the Listaller source tarball contains more examples about how to use BinReloc.