Guide to Making Relocatable Applications (BinReloc 2.0)

  1. The problem
  2. The solution
  3. Hello World
  4. Initialization
  5. Basic usage
  6. Glib-style API
  7. Useful utility functions
  8. Autoconf/automake build system integration
  9. KDE integration
  10. Full API reference
  11. More examples
  12. BinReloc 1.x

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

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:

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

Step 1: generate BinReloc source files

Joe downloads the BinReloc SDK from the Developer 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

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/

Step 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.

Step 4: 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/example/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

Initialization

BinReloc must be initialize by calling one of the BinReloc initialization functions:

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).

Basic usage

There are more functions besides br_find_exe(). Here is a list of all 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/CoolApp2000XP/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.

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.

Inline documentation

All BinReloc functions have inline documentation! So just take a look in binreloc.c if you need more info about a certain function.

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.

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);

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 \ 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:

Users can always disable BinReloc manually by passing --disable-binreloc to the configure script.

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(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));

Full API reference

More examples

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

BinReloc 1.x

The guide for BinReloc version 1.x is still available for those who need it.