1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
|
<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!ENTITY % BOOK_ENTITIES SYSTEM "Listaller.ent">
%BOOK_ENTITIES;
]>
<section id="sect-Listaller-App-Development-Relocation">
<title>Guide to making relocatable applications</title>
<section id="relocation-problem">
<title>The problem</title>
<para>
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.
</para>
<para>
On Win32, applications and libraries are easily relocatable because
applications and DLLs can use <code>GetModuleFilename()</code> to obtain
their full path.
</para>
<para>
On Linux however, no easy mechanisms exist. There is no function
equivalent to <code>GetModuleFilename()</code>.
For executables, you can still find your full location by resolving the symlink <filename>/proc/self/exe</filename>,
but that won't work for libraries.
</para>
</section>
<section id="relocation-solution">
<title>The solution</title>
<para>
This is why we have developed <application>BinReloc</application>.
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.
</para>
<itemizedlist>
<title>Highlights</title>
<listitem><para>It can be statically included in your project.</para></listitem>
<listitem><para>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).</para></listitem>
<listitem><para>It has absolutely no dependancies other than libc.</para></listitem>
<listitem><para>It's <emphasis>public domain</emphasis>, which means you can do anything you want with the code, including relicensing it under a different license.</para></listitem>
<listitem><para>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.</para></listitem>
<listitem><para>There are two versions of BinReloc: a plain C version, and glib version which even has a glib-style API.</para></listitem>
</itemizedlist>
<note>
<title>Tip for KDE developers</title>
<para>As of April 21 2004, BinReloc-like functionality has been added to the KDE-libs, in the
<code>KStandardDirs</code> class. If your application uses <code>KStandardDirs</code>
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.</para>
</note>
</section>
<section id="relocation-helloworld">
<title>Hello World!</title>
<para>
Let's begin with a BinReloc "Hello World" tutorial. We will use the <emphasis>plain C</emphasis> 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 <application>Hello World</application> program.</para>
<procedure>
<step><para>Generate BinReloc source files</para>
<para>Joe <ulink url="http://listaller.tenstral.net">downloads the BinReloc SDK</ulink> from the <emphasis>Listaller Tools</emphasis> section of the download page.
He extracts the archive in his home folder. A folder called <filename>binreloc-2.0</filename> appears.</para>
<programlisting language="Bash"><![CDATA[[joe@localhost /home/joe]$ tar xzf binreloc-2.0.tar.gz
[joe@localhost /home/joe]$ cd binreloc-2.0</div>]]>
</programlisting>
<para>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:</para>
<programlisting language="Bash"><![CDATA[[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>]]>
</programlisting>
</step>
<step><para>Write the program</para>
<para>Now that Joe has generated the BinReloc source files, he continues with writing a Hello World program:</para>
<programlisting language="C++"><![CDATA[#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;
}]]>
</programlisting>
<para>He saves this file as <filename>/home/joe/helloworld/hello.c</filename>.</para>
</step>
<step><para>Compile & run</para>
<para>Now it is time to compile & run the program:</para>
<programlisting language="Bash"><![CDATA[[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]]>
</programlisting>
<para>Yes, it's this easy!</para>
<note>
<title>How to disable BinReloc</title>
<para>
The <code>-DENABLE_BINRELOC</code> 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:
</para>
<programlisting language="Bash"><![CDATA[[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]]>
</programlisting>
</note>
</step>
</procedure>
</section>
<section id="relocation-init">
<title>Initialization</title>
<para>
BinReloc <emphasis>must</emphasis> be initialize by calling one of the BinReloc initialization functions:
</para>
<para>
If you're using BinReloc in an application, then call <code>br_init()</code>. The definition is:
</para>
<programlisting language="Bash"><![CDATA[int br_init (BrInitError *error);]]></programlisting>
<para>
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 <code>error</code>.
The following error codes are available:
</para>
<programlisting language="Bash"><![CDATA[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;
]]>
</programlisting>
<para>
If you're using BinReloc in a library, then call <code>br_init_lib()</code>. The definition is:
<code>int br_init_lib (BrInitError *error);</code>
</para>
<para>This function returns 1 on success, and 0 if BinReloc failed to initialize.</para>
<para>
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 <code>ENABLE_BINRELOC</code> is not defined), or because the application
is running on a platform which doesn't support relocating executables (non-Linux platforms).
</para>
</section>
<section id="relocation-basic">
<title>Basic usage</title>
<para>There are more functions besides <code>br_find_exe()</code>. Here is a list of all relocation functions:</para>
<table frame="all">
<title>Relocation functions</title>
<tgroup cols='2' colsep='1' rowsep='1'>
<thead>
<row>
<entry>Function</entry>
<entry>Returns</entry>
</row>
</thead>
<tbody>
<row>
<entry><para><code>br_find_exe()</code></para></entry>
<entry><para>The full path of your application or library.</para></entry>
</row>
<row>
<entry><para><code>br_find_exe_dir()</code></para></entry>
<entry><para>The folder in which your application or library is located.</para></entry>
</row>
<row>
<entry><para><code>br_find_prefix()</code></para></entry>
<entry>
<para>
The prefix in which your application or library is located.
This function assumes that your binary is located
inside an FHS-compatible directory structure (<filename>$prefix/bin/</filename> or <filename>$prefix/lib/</filename>). Examples:
</para>
<itemizedlist>
<listitem><para>Your binary is <filename>/usr/bin/foo</filename>. It will return <filename>/usr</filename>.</para></listitem>
<listitem><para>Your library is <filename>/usr/local/lib/libfoo.so</filename>. It will return <filename>/usr/local</filename>.</para></listitem>
<listitem><para>Your binary is <filename>/Applications/CoolApp2040XP/CoolApp</filename>. It will return <filename>/Applications"</filename>.</para></listitem>
</itemizedlist>
<para>So basically, it returns <emphasis>dirname(executable_filename) + "/.."</emphasis></para>
</entry>
</row>
<row>
<entry><para><code>br_find_bin_dir()</code></para></entry>
<entry><para>PREFIX + "/bin"</para></entry>
</row>
<row>
<entry><para><code>br_find_sbin_dir()</code></para></entry>
<entry><para>PREFIX + "/sbin"</para></entry>
</row>
<row>
<entry><para><code>br_find_data_dir()</code></para></entry>
<entry><para>PREFIX + "/share"</para></entry>
</row>
<row>
<entry><para><code>br_find_locale_dir()</code></para></entry>
<entry><para>PREFIX + "/locale"</para></entry>
</row>
<row>
<entry><para><code>br_find_lib_dir()</code></para></entry>
<entry><para>PREFIX + "/lib"</para></entry>
</row>
<row>
<entry><para><code>br_find_libexec_dir()</code></para></entry>
<entry><para>PREFIX + "/libexec"</para></entry>
</row>
<row>
<entry><para><code>br_find_etc_dir()</code></para></entry>
<entry><para>PREFIX + "/etc"</para></entry>
</row>
</tbody>
</tgroup>
</table>
<para>
All functions in the above table are declared like this:
<code>char *br_find_something (const char *default_path);</code>
</para>
<para>
<code>default_path</code> 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.
</para>
<warning>
<para>
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.
</para>
</warning>
<para>
<emphasis>All</emphasis> BinReloc functions have inline documentation! So just take a look at <filename>binreloc.c</filename> if you need more info about a certain function.
</para>
</section>
<section id="relocation-glib">
<title>GLib-style API</title>
<para>
There's also a BinReloc version with a glib-style API. Generating this version is just like generating the normal version:
</para>
<programlisting language="Bash"><![CDATA[[joe@localhost /home/joe/binreloc-2.0]$ ./generate.pl glib
Source code written to 'binreloc.c'
Header written to 'binreloc.h']]>
</programlisting>
<para>
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 <ulink url="http://listaller.tenstral.net">the full API reference</ulink>.
</para>
</section>
<section id="relocation-utility">
<title>Useful utility functions</title>
<para>
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.
</para>
<programlisting language="C">char *br_strcat (const char *str1, const char *str2);</programlisting>
<itemizedlist>
<listitem><para>str1: A string.</para></listitem>
<listitem><para>str2: Another string.</para></listitem>
<listitem><para>Returns: A newly-allocated string. This string should be freed when no longer needed.</para></listitem>
</itemizedlist>
<para>Concatenate str1 and str2 to a newly allocated string.</para>
<para>Example:</para>
<programlisting language="C"><![CDATA[char *datafile;
datafile = br_strcat ("/usr", "/foo/mydata.txt");
load_data_file (datafile);
free (datafile);</pre>
]]>
</programlisting>
</section>
<section id="relocation-autoconf">
<title>Autoconf/Automake build system integration</title>
<para>
Most Autoconf/Automake projects use macros that define a hardcoded path.
Let's take a look at this piece of code as example.
</para>
<para>In <filename>Makefile.am</filename>:</para>
<programlisting><![CDATA[INCLUDES = $(LIBGLADE_CFLAGS) \
-DDATADIR=\"$(datadir)\"
bin_PROGRAMS = foo
foo_SOURCES = main.c]]>
</programlisting>
<para>In <filename>main.c</filename>:</para>
<programlisting language="C">xml = glade_xml_new (DATADIR "/foobar/glade/main.glade", NULL, NULL);</programlisting>
<para>How to use BinReloc:</para>
<procedure>
<step>
<para>
Use the special BinReloc Autoconf Macro (<filename>binreloc.m4</filename>). This file can be found in
the <ulink url="http://listaller.tenstral.net">BinReloc SDK</ulink>.
</para>
<para>
Append the contents of <filename>binreloc.m4</filename> to <filename>acinclude.m4</filename> (which is in the same
folder as <filename>configure.in</filename>). Create <filename>acinclude.m4</filename> if it doesn't exist.
</para>
<para>
In <filename>configure.in</filename>, put the command <code>AM_BINRELOC</code> somewhere.
</para>
<para>
The <literal>AM_BINRELOC</literal> macro checks whether BinReloc should be enabled (whether the system supports the feature,
whether the user explicitly disabled it, etc). The variable <code>$br_cv_binreloc</code>
will be set to 'yes' if BinReloc is enabled, or 'no' otherwise.
</para>
</step>
<step>
<para>Copy <filename>binreloc.c</filename> and <filename>binreloc.h</filename> to your source code directory.</para>
</step>
<step>
<para>Add <code>BINRELOC_CFLAGS</code> and <filename>binreloc.c/binreloc.h</filename> to <filename>Makefile.am</filename>:</para>
<programlisting>
<![CDATA[AM_CPPFLAGS = $(BINRELOC_CFLAGS)
...
foo_SOURCES = main.c <span class="highlight">\
binreloc.h \
binreloc.c]]>
</programlisting>
</step>
<step>
<para>At the beginning of <filename>main.c</filename>, add:</para>
<programlisting language="C">#include "binreloc.h"</programlisting>
<para>
Somewhere in <filename>main.c</filename>:
</para>
<programlisting language="C">
<![CDATA[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);]]>
</programlisting>
</step>
</procedure>
<para>
And that was it! Your configure script will now have a <emphasis>--enable-binreloc=[yes/no/auto]</emphasis> option.
The default value for this option is <emphasis>--enable-binreloc=auto</emphasis>, which will automatically check whether BinReloc support is
desired. It does so by checking for the following things:
</para>
<itemizedlist>
<listitem><para>Whether <filename>/proc/self/maps</filename> is available.</para></listitem>
<listitem><para>Whether the user told configure to use a different location for a specific directory, such as by passing <emphasis>--bindir=/foo/bin</emphasis>.</para></listitem>
</itemizedlist>
<para>Users can always disable BinReloc manually by passing <emphasis>--disable-binreloc</emphasis> to the <emphasis>configure</emphasis> script.</para>
</section>
<section id="relocation-kde">
<title>KDE integration</title>
<note>
<title>Note to KDE developers</title>
<para>
As of April 21 2004, BinReloc-like functionality has been added to the KDE-libs, in the
<code>KStandardDirs</code> class. If your <emphasis>application</emphasis> uses <code>KStandardDirs</code>
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.
</para>
</note>
<para>In your program's initialization function, add the following code:</para>
<programlisting language="C++"><![CDATA[KGlobal::dirs()->addPrefix(br_find_prefix(DEFAULT_PREFIX));]]></programlisting>
<para>
Make sure you use <code>KGlobal::dirs()</code> to lookup data files througout your entire program.
If you create new instances of <code>KStandardDirs</code>, you need the re-add the prefix.
</para>
<para>
If you want to use <code>KIconLoader</code> to load icons from whever your program is installed, add this:
</para>
<programlisting language="C++"><![CDATA[KGlobal::iconLoader()->addAppDir(br_find_data_dir(DEFAULT_DATA_DIR));]]></programlisting>
</section>
<section id="relocation-api">
<title>Full API reference</title>
<para>... will be available very soon!</para>
</section>
<section id="relocation-more">
<title>More examples</title>
<para>
The <filename>contrib/binreloc/tests</filename> folder in the Listaller source tarball contains more examples about how to use BinReloc.
</para>
</section>
</section>
|