Back to the Autopackage main page

Guide to Making Relocatable Applications

  1. The problem
  2. The solution
  3. Hello World
  4. Basic usage
  5. Useful utility functions
  6. Implementation details
  7. Autoconf/automake build system integration
  8. KDE integration

The problem

Autopackage 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 hardcoded 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.

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 aplication or library. Highlights:

Historical note: BinReloc is the successor of libprefixdb, Autopackage's previous solution to the relocation problem. BinReloc is more automatic and easier to use.

Note to KDE developers:
As of April 21 2004, BinReloc-like functionality has been added to the KDE CVS, 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.

Hello World

Let's begin with a BinReloc "Hello World" tutorial.

Step 1:

Download BinReloc from the Developer Tools section of the download page. Extract the archive, and you will find the following files:
  • README
  • ChangeLog
  • Makefile
  • USAGE
  • binreloc.m4
  • prefix.c
  • prefix.h
  • libtest.c
  • test.c
The only files you need are prefix.c and prefix.h.

Step 2:

Create the folder ~/helloworld. Copy prefix.c and prefix.h to that folder. Open a terminal and change to that folder.

Step 3:

Open your favorite text editor and save the following code to ~/helloworld/hello.c:
#include <stdio.h>
#include "prefix.h"

int main () {
    printf ("The full path of this application is: %s\n", SELFPATH);
    return 0;
}

Step 4:

Compile the program and run it:
[bash ~/helloworld]$ gcc -DENABLE_BINRELOC hello.c prefix.c -o hello -lpthread
[bash ~/helloworld]$ ./hello
The full path of this application is: /home/example/helloworld/hello

Yes, it's this easy! Now let's take a look at what the code does:

Basic usage

There are more macros available. One of them is PREFIX.
It returns the prefix of your binary. This macro assumes that your binary is located inside an FHS-compatible directory structure ($prefix/bin/ or $prefix/lib/).
Examples: So basically, it returns SELFPATH + "../.."

Other similar macros are:
Macro Value
BINDIR PREFIX + "/bin"
SBINDIR PREFIX + "/sbin"
DATADIR PREFIX + "/share"
LIBDIR PREFIX + "/lib"
LIBEXECDIR PREFIX + "/libexec"
ETCDIR PREFIX + "/etc"
SYSCONFDIR Alias for ETCDIR.
CONFDIR Alias for ETCDIR.
LOCALEDIR DATADIR + "/locale"

There are also a set of macros that are named exactly the same as the above macros, but are prepended with BR_. For example, BR_SELFPATH and BR_DATADIR. These macros are convenience macros for contatenating paths. BR_SELFPATH("/lib") is like SELFPATH + "/lib", BR_DATADIR("/data.png") is like DATADIR + "/data.png", etc.
Note: The return values must not be freed. See Implementation details for information about why.

All of the afore mentioned macros (including SELFPATH, and PREFIX) can be disabled by defining BR_NO_MACROS.

Useful utility functions

BinReloc provides the utility function br_strcat. Most applications that deal with paths will probably need to concatenate strings somewhere in the source code. For that reason, this function is provided by BinReloc as a utility function.
br_strcat is 100% portable across all operating systems! You can use it even when ENABLE_BINRELOC isn't defined.
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. This is a
convenience function since many applications need to concatenate DATADIR
with another path.

Example:

char *datafile;

/* Note: this is kind of redundant because you can also just do
   BR_DATADIR("/foo/mydata.txt") but this is just an example ;) */
datafile = br_strcat (DATADIR, "/foo/mydata.txt");
load_data_file (datafile);
free (datafile);

Implementation details

All macros use br_thread_local_store() internally, so that you don't have to worry about freeing the string returned by those macros. It's defined as follows: const char *br_thread_local_store (char *str);

The first time you call br_thread_local_store(), str is saved in an thread local internal variable (refer to the pthreads manual for more information about this), or a normal internal variable if pthread support is disabled. str remains unmodified and is returned as const char *. The next times you call this function, the previous value of str (that was saved in the internal variable) is freed. Then the current str is stored in that variable again, and str is returned as const char *. The internal variable is also freed on application exit.

This means that you should not free a string once it is passed to br_thread_local_store(), it already does that for you. You also cannot pass static strings to br_thread_local_store().

So what does this mean for all the macros?

Example:

char *foo, *bar, *saved;

/* => "/usr/share/mydata.txt" */
foo = BR_DATADIR("mydata.txt");  
saved = strdup(foo);

/* => "/usr/share/moredata.png"; "/usr/share/mydata.txt" is now freed */
bar = BR_DATADIR("moredata.png");

/* This is wrong! You can't do this because the
   value of foo is freed by the 2nd BR_DATADIR call */
printf("%s\n", foo);

/* This is good; by calling strdup() the value is saved.
   But you have to free this variable yourself. */
printf("%s\n", saved);

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 source code.

    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 prefix.c and prefix.h to your source code directory.
  3. Add BINRELOC_CFLAGS and prefix.c/prefix.h to Makefile.am, as well as additional linker flags:
    AM_CPPFLAGS = $(BINRELOC_CFLAGS)
    ...
    foo_SOURCES = main.c \
                  prefix.h \
                  prefix.c
    foo_LDFLAGS = -lpthread

    If the pthread dependancy is unacceptable, then you can disable pthread support by defining BR_PTHREAD=0:

    AM_CPPFLAGS = $(BINRELOC_CFLAGS) -DBR_PTHREAD=0
  4. At the beginning of main.c, add:
    #include "prefix.h"

    Somewhere in main.c:

    xml = glade_xml_new (BR_DATADIR("/foobar/glade/main.glade"), NULL, NULL);

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:

Users can always disable BinReloc manually by passing --disable-binreloc.

KDE integration

Note to KDE developers:
As of April 21 2004, BinReloc-like functionality has been added to the KDE CVS, 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(strdup(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(strdup(DATADIR));