Branch data Line data Source code
1 : : /*
2 : : * BinReloc - a library for creating relocatable executables
3 : : * Written by: Hongli Lai <h.lai@chello.nl>
4 : : * http://autopackage.org/
5 : : *
6 : : * This source code is public domain. You can relicense this code
7 : : * under whatever license you want.
8 : : *
9 : : * See http://autopackage.org/docs/binreloc/ for
10 : : * more information and how to use this.
11 : : */
12 : : /********************************************************************\
13 : : * This program is free software; you can redistribute it and/or *
14 : : * modify it under the terms of the GNU General Public License as *
15 : : * published by the Free Software Foundation; either version 2 of *
16 : : * the License, or (at your option) any later version. *
17 : : * *
18 : : * This program is distributed in the hope that it will be useful, *
19 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
20 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21 : : * GNU General Public License for more details. *
22 : : * *
23 : : * You should have received a copy of the GNU General Public License*
24 : : * along with this program; if not, contact: *
25 : : * *
26 : : * Free Software Foundation Voice: +1-617-542-5942 *
27 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
28 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
29 : : * *
30 : : \********************************************************************/
31 : :
32 : :
33 : : #ifndef __BINRELOC_C__
34 : : #define __BINRELOC_C__
35 : : #include <config.h>
36 : :
37 : : #include <platform.h>
38 : : #if PLATFORM(WINDOWS)
39 : : #include <windows.h>
40 : : #endif
41 : :
42 : : #ifdef ENABLE_BINRELOC
43 : : #include <sys/types.h>
44 : : #include <sys/stat.h>
45 : : #include <unistd.h>
46 : : #include <stdint.h>
47 : : #ifdef MAC_INTEGRATION
48 : : #include <gtkmacintegration/gtkosxapplication.h>
49 : : #elif GNC_PLATFORM_OSX
50 : : #include <mach-o/dyld.h>
51 : : #endif
52 : : #endif /* ENABLE_BINRELOC */
53 : : #include <stdio.h>
54 : : #include <stdlib.h>
55 : : #include <limits.h>
56 : : #include <string.h>
57 : : #include "binreloc.h"
58 : : #include "gnc-filepath-utils.h"
59 : : #include <glib.h>
60 : : #include "gncla-dir.h"
61 : :
62 : : G_BEGIN_DECLS
63 : :
64 : : /** @internal
65 : : * Find the canonical filename of the executable. Returns the filename
66 : : * (which must be freed) or NULL on error. If the parameter 'error' is
67 : : * not NULL, the error code will be stored there, if an error occurred.
68 : : */
69 : : static char *
70 : 0 : _br_find_exe (Gnc_GbrInitError *error)
71 : : {
72 : : #ifndef ENABLE_BINRELOC
73 : : if (error)
74 : : *error = GNC_GBR_INIT_ERROR_DISABLED;
75 : : return NULL;
76 : : #elif defined G_OS_WIN32
77 : : /* N.B. g_win32_get_package_installation_directory_of_module returns the
78 : : * parent if the last element of the directory is "bin" or "lib", but
79 : : * otherwise the directory itself. We assume that gnucash.exe isn't in lib.
80 : : */
81 : : gchar *prefix = g_win32_get_package_installation_directory_of_module (NULL);
82 : : gchar *result = g_build_filename (prefix, "bin", "gnucash.exe", NULL);
83 : : if (prefix == NULL)
84 : : {
85 : : if (error)
86 : : *error = GNC_GBR_INIT_WIN32_NO_EXE_DIR;
87 : : return NULL;
88 : : }
89 : : if (!g_file_test (result, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE))
90 : : {
91 : : g_free (result);
92 : : result = g_build_filename (prefix, "gnucash.exe", NULL);
93 : : if (!g_file_test (result,
94 : : G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE))
95 : : {
96 : : g_free (result);
97 : : result = NULL;
98 : : if (error)
99 : : *error = GNC_GBR_INIT_WIN32_NO_EXE_DIR;
100 : : }
101 : : }
102 : : g_free (prefix);
103 : : return result;
104 : : #else
105 : : char path[PATH_MAX + 1], path2[PATH_MAX + 1];
106 : : char *line, *result;
107 : 0 : size_t buf_size = PATH_MAX + 1;
108 : : FILE *f;
109 : :
110 : : #ifdef MAC_INTEGRATION
111 : : result = gtkosx_application_get_executable_path();
112 : : strncpy (path2, result, buf_size - 1);
113 : : g_free (result);
114 : : g_print ("Application Path %s\n", path2);
115 : : #elif defined GNC_PLATFORM_OSX
116 : : /* Native Mac, but not Aqua */
117 : : uint32_t size2 = buf_size;
118 : : if (_NSGetExecutablePath (path2, &size2) != 0)
119 : : {
120 : : /* buffer not big enough or some other error */
121 : : if (error)
122 : : *error = GNC_GBR_INIT_ERROR_NOMEM;
123 : : return NULL;
124 : : }
125 : : #else
126 : 0 : strncpy (path2, "/proc/self/exe", buf_size - 1);
127 : : #endif
128 : :
129 : : /* Follow all sym links */
130 : 0 : if (realpath (path2, path) != NULL)
131 : : {
132 : 0 : return g_strdup (path);
133 : : }
134 : :
135 : : /* realpath() failed; this can happen when the program is
136 : : * running in Valgrind 2.2. Read from /proc/self/maps as fallback. */
137 : :
138 : 0 : buf_size = PATH_MAX + 128;
139 : 0 : line = (char *) g_try_malloc (buf_size);
140 : 0 : if (line == NULL)
141 : : {
142 : : /* Cannot allocate memory. */
143 : 0 : if (error)
144 : 0 : *error = GNC_GBR_INIT_ERROR_NOMEM;
145 : 0 : return NULL;
146 : : }
147 : :
148 : 0 : f = fopen ("/proc/self/maps", "r");
149 : 0 : if (f == NULL)
150 : : {
151 : 0 : g_free (line);
152 : 0 : if (error)
153 : 0 : *error = GNC_GBR_INIT_ERROR_OPEN_MAPS;
154 : 0 : return NULL;
155 : : }
156 : :
157 : : /* The first entry should be the executable name. */
158 : 0 : result = fgets (line, (int) buf_size, f);
159 : 0 : if (result == NULL)
160 : : {
161 : 0 : fclose (f);
162 : 0 : g_free (line);
163 : 0 : if (error)
164 : 0 : *error = GNC_GBR_INIT_ERROR_READ_MAPS;
165 : 0 : return NULL;
166 : : }
167 : :
168 : : /* Get rid of newline character. */
169 : 0 : buf_size = strlen (line);
170 : 0 : if (buf_size <= 0)
171 : : {
172 : : /* Huh? An empty string? */
173 : 0 : fclose (f);
174 : 0 : g_free (line);
175 : 0 : if (error)
176 : 0 : *error = GNC_GBR_INIT_ERROR_INVALID_MAPS;
177 : 0 : return NULL;
178 : : }
179 : 0 : if (line[buf_size - 1] == 10)
180 : 0 : line[buf_size - 1] = 0;
181 : :
182 : : /* Extract the filename; it is always an absolute path. */
183 : 0 : result = strchr (line, '/');
184 : :
185 : : /* Sanity check. */
186 : 0 : if (strstr (line, " r-xp ") == NULL || result == NULL)
187 : : {
188 : 0 : fclose (f);
189 : 0 : g_free (line);
190 : 0 : if (error)
191 : 0 : *error = GNC_GBR_INIT_ERROR_INVALID_MAPS;
192 : 0 : return NULL;
193 : : }
194 : :
195 : 0 : result = g_strdup (result);
196 : 0 : fclose (f);
197 : 0 : g_free (line);
198 : 0 : return result;
199 : : #endif /* ENABLE_BINRELOC */
200 : : }
201 : :
202 : :
203 : :
204 : : static gchar *exe = NULL;
205 : :
206 : : static void set_gerror (GError **error, Gnc_GbrInitError errcode);
207 : :
208 : :
209 : 0 : void gnc_gbr_set_exe (const gchar* default_exe)
210 : : {
211 : 0 : if (exe != NULL)
212 : 0 : g_free(exe);
213 : 0 : exe = NULL;
214 : :
215 : 0 : if (default_exe != NULL)
216 : 0 : exe = g_strdup(default_exe);
217 : 0 : }
218 : :
219 : :
220 : : /** Initialize the BinReloc library (for applications).
221 : : *
222 : : * This function must be called before using any other BinReloc functions.
223 : : * It attempts to locate the application's canonical filename.
224 : : *
225 : : * @note If you want to use BinReloc for a library, then you should call
226 : : * gnc_gbr_init_lib() instead.
227 : : *
228 : : * @param error If BinReloc failed to initialize, then the error report will
229 : : * be stored in this variable. Set to NULL if you don't want an
230 : : * error report. See the #Gnc_GbrInitError for a list of error
231 : : * codes.
232 : : *
233 : : * @returns TRUE on success, FALSE if BinReloc failed to initialize.
234 : : */
235 : : gboolean
236 : 0 : gnc_gbr_init (GError **error)
237 : : {
238 : 0 : Gnc_GbrInitError errcode = 0;
239 : :
240 : : /* Locate the application's filename. */
241 : 0 : exe = _br_find_exe (&errcode);
242 : 0 : if (exe != NULL)
243 : : /* Success! */
244 : 0 : return TRUE;
245 : : else
246 : : {
247 : : /* Failed :-( */
248 : 0 : set_gerror (error, errcode);
249 : 0 : return FALSE;
250 : : }
251 : : }
252 : :
253 : :
254 : : static void
255 : 0 : set_gerror (GError **error, Gnc_GbrInitError errcode)
256 : : {
257 : : gchar *error_message;
258 : :
259 : 0 : if (error == NULL)
260 : 0 : return;
261 : :
262 : 0 : switch (errcode)
263 : : {
264 : 0 : case GNC_GBR_INIT_ERROR_NOMEM:
265 : 0 : error_message = "Cannot allocate memory.";
266 : 0 : break;
267 : 0 : case GNC_GBR_INIT_ERROR_OPEN_MAPS:
268 : 0 : error_message = "Unable to open /proc/self/maps for reading.";
269 : 0 : break;
270 : 0 : case GNC_GBR_INIT_ERROR_READ_MAPS:
271 : 0 : error_message = "Unable to read from /proc/self/maps.";
272 : 0 : break;
273 : 0 : case GNC_GBR_INIT_ERROR_INVALID_MAPS:
274 : 0 : error_message = "The file format of /proc/self/maps is invalid.";
275 : 0 : break;
276 : 0 : case GNC_GBR_INIT_ERROR_DISABLED:
277 : 0 : error_message = "Binary relocation support is disabled.";
278 : 0 : break;
279 : 0 : case GNC_GBR_INIT_ERROR_MAC_NOT_BUNDLE:
280 : 0 : error_message = "BinReloc determined that gnucash is not running from a bundle";
281 : 0 : break;
282 : 0 : case GNC_GBR_INIT_ERROR_MAC_NOT_APP_BUNDLE:
283 : 0 : error_message = "Binreloc determined that the bundle is not an app bundle";
284 : 0 : break;
285 : 0 : case GNC_GBR_INIT_WIN32_NO_EXE_DIR:
286 : 0 : error_message = "Binreloc was unable to determine the location of gnucash.exe.";
287 : 0 : break;
288 : 0 : default:
289 : 0 : error_message = "Unknown error.";
290 : 0 : break;
291 : : };
292 : 0 : g_set_error (error, g_quark_from_static_string ("GBinReloc"),
293 : : errcode, "%s", error_message);
294 : : }
295 : :
296 : :
297 : : /** Find the canonical filename of the current application.
298 : : *
299 : : * @param default_exe A default filename which will be used as fallback.
300 : : * @returns A string containing the application's canonical filename,
301 : : * which must be freed when no longer necessary. If BinReloc is
302 : : * not initialized, or if the initialization function failed,
303 : : * then a copy of default_exe will be returned. If default_exe
304 : : * is NULL, then NULL will be returned.
305 : : */
306 : : gchar *
307 : 0 : gnc_gbr_find_exe (const gchar *default_exe)
308 : : {
309 : 0 : if (exe == NULL)
310 : : {
311 : : /* BinReloc is not initialized. */
312 : 0 : if (default_exe != NULL)
313 : 0 : return g_strdup (default_exe);
314 : : else
315 : 0 : return NULL;
316 : : }
317 : 0 : return g_strdup (exe);
318 : : }
319 : :
320 : :
321 : : /** Locate the directory in which the current application is installed.
322 : : *
323 : : * The prefix is generated by the following pseudo-code evaluation:
324 : : * \code
325 : : * dirname(exename)
326 : : * \endcode
327 : : *
328 : : * @param default_dir A default directory which will used as fallback.
329 : : * @return A string containing the directory, which must be freed when no
330 : : * longer necessary. If BinReloc is not initialized, or if the
331 : : * initialization function failed, then a copy of default_dir
332 : : * will be returned. If default_dir is NULL, then NULL will be
333 : : * returned.
334 : : */
335 : : gchar *
336 : 0 : gnc_gbr_find_exe_dir (const gchar *default_dir)
337 : : {
338 : 0 : if (exe == NULL)
339 : : {
340 : : /* BinReloc not initialized. */
341 : 0 : if (default_dir != NULL)
342 : 0 : return g_strdup (default_dir);
343 : : else
344 : 0 : return NULL;
345 : : }
346 : :
347 : 0 : return g_path_get_dirname (exe);
348 : : }
349 : :
350 : :
351 : : /** Locate the prefix in which the current application is installed.
352 : : *
353 : : * The prefix is generated by the following pseudo-code evaluation:
354 : : * \code
355 : : * dirname(dirname(exename))
356 : : * \endcode
357 : : *
358 : : * @param default_prefix A default prefix which will used as fallback.
359 : : * @return A string containing the prefix, which must be freed when no
360 : : * longer necessary. If BinReloc is not initialized, or if the
361 : : * initialization function failed, then a copy of default_prefix
362 : : * will be returned. If default_prefix is NULL, then NULL will be
363 : : * returned.
364 : : */
365 : :
366 : : static inline gchar *
367 : 2 : get_mac_bundle_prefix()
368 : : {
369 : : #if defined ENABLE_BINRELOC && defined MAC_INTEGRATION
370 : : gchar *id = gtkosx_application_get_bundle_id ();
371 : : gchar *path = gtkosx_application_get_resource_path ();
372 : : gchar *basename = g_path_get_basename (path);
373 : : /* If id is nullthe app is unbundled and the path
374 : : is just the path to the application directory.
375 : : We already have that and our version is better.
376 : : If GNC_UNINSTALLED is set then we're running from
377 : : GNC_BUILDDIR.
378 : : */
379 : : if (id == NULL || g_getenv ("GNC_UNINSTALLED"))
380 : : {
381 : : g_free (basename);
382 : : g_free (path);
383 : : g_free (id);
384 : : return NULL;
385 : : }
386 : :
387 : : g_free (id);
388 : :
389 : : if (g_strcmp0 ("bin", basename) == 0)
390 : : {
391 : : g_free (path);
392 : : g_free (basename);
393 : : return NULL;
394 : : }
395 : :
396 : : g_free (basename);
397 : :
398 : : return path;
399 : : #endif
400 : 2 : return NULL;
401 : : }
402 : :
403 : : static inline gchar*
404 : 447 : get_builddir_prefix()
405 : : {
406 : 447 : if (g_getenv ("GNC_UNINSTALLED"))
407 : 445 : return g_strdup (g_getenv ("GNC_BUILDDIR"));
408 : 2 : return NULL;
409 : : }
410 : :
411 : : gchar *
412 : 447 : gnc_gbr_find_prefix (const gchar *default_prefix)
413 : : {
414 : : gchar *dir1, *dir2;
415 : 447 : if ((dir2 = get_builddir_prefix()) || (dir2 = get_mac_bundle_prefix()))
416 : 445 : return dir2;
417 : 2 : if (exe == NULL)
418 : : {
419 : : /* BinReloc not initialized. */
420 : 2 : if (default_prefix != NULL)
421 : 0 : return g_strdup (default_prefix);
422 : : else
423 : 2 : return NULL;
424 : : }
425 : 0 : dir1 = g_path_get_dirname (exe);
426 : 0 : dir2 = g_path_get_dirname (dir1);
427 : 0 : g_free (dir1);
428 : 0 : return dir2;
429 : : }
430 : :
431 : : /* Locate a specified component directory.
432 : : *
433 : : * E.g., <prefix>/share
434 : :
435 : : * default_dir is passed in from the wrapper function, compiled_dir is the corresponding constant from gncla-dir.h.
436 : : *
437 : : * If compiled_dir exists and is an absolute path then we check the dynamic
438 : : * prefix and if it's NULL fall back first on the passed-in default and then on
439 : : * compiled_dir;
440 : : * otherwise if the dynamic prefix turns out to be the compile time defined PREFIX
441 : : * just use that
442 : : * otherwise we pass the compiled PREFIX value as a default to
443 : : * gnc_gbr_find_prefix, remove the PREFIX part (if any) from the compiled_dir
444 : : * and append that to the retrieved prefix.
445 : : */
446 : : static gchar*
447 : 432 : find_component_directory (const gchar *default_dir, const gchar* compiled_dir)
448 : : {
449 : 432 : gchar *prefix = NULL, *dir = NULL;
450 : 432 : gchar *subdir = gnc_file_path_relative_part(PREFIX, compiled_dir);
451 : :
452 : 432 : prefix = gnc_gbr_find_prefix (NULL);
453 : 432 : if (prefix == NULL)
454 : : {
455 : 2 : g_free (subdir);
456 : 2 : return g_strdup (default_dir ? default_dir : compiled_dir);
457 : : }
458 : 430 : if (!g_getenv("GNC_UNINSTALLED"))
459 : : {
460 : 0 : if (!g_strcmp0 (prefix, PREFIX))
461 : : {
462 : 0 : g_free (subdir);
463 : 0 : g_free (prefix);
464 : 0 : return g_strdup (compiled_dir);
465 : : }
466 : :
467 : 0 : if (g_strcmp0 (compiled_dir, subdir) == 0)
468 : : {
469 : : /* compiled_dir isn't a subdir of PREFIX. This isn't relocatable so
470 : : * return compiled_dir.
471 : : */
472 : 0 : g_free (subdir);
473 : 0 : g_free (prefix);
474 : 0 : return g_strdup (compiled_dir);
475 : : }
476 : : }
477 : 430 : dir = g_build_filename (prefix, subdir, NULL);
478 : 430 : g_free (subdir);
479 : 430 : g_free (prefix);
480 : 430 : return dir;
481 : : }
482 : :
483 : :
484 : : /** Locate the application's binary folder.
485 : : *
486 : : * The path is generated by the following pseudo-code evaluation:
487 : : * \code
488 : : * prefix + "/bin"
489 : : * \endcode
490 : : *
491 : : * @param default_bin_dir A default path which will used as fallback.
492 : : * @return A string containing the bin folder's path, which must be freed when
493 : : * no longer necessary. If BinReloc is not initialized, or if the
494 : : * initialization function failed, then a copy of default_bin_dir will
495 : : * be returned. If default_bin_dir is NULL, then NULL will be returned.
496 : : */
497 : : gchar *
498 : 2 : gnc_gbr_find_bin_dir (const gchar *default_bin_dir)
499 : : {
500 : 2 : return find_component_directory (default_bin_dir, BINDIR);
501 : : }
502 : :
503 : : /** Locate the application's data folder.
504 : : *
505 : : * The path is generated by the following pseudo-code evaluation:
506 : : * \code
507 : : * prefix + "/share"
508 : : * \endcode
509 : : *
510 : : * @param default_data_dir A default path which will used as fallback.
511 : : * @return A string containing the data folder's path, which must be freed when
512 : : * no longer necessary. If BinReloc is not initialized, or if the
513 : : * initialization function failed, then a copy of default_data_dir
514 : : * will be returned. If default_data_dir is NULL, then NULL will be
515 : : * returned.
516 : : */
517 : : gchar *
518 : 305 : gnc_gbr_find_data_dir (const gchar *default_data_dir)
519 : : {
520 : 305 : return find_component_directory (default_data_dir, DATADIR);
521 : : }
522 : :
523 : : /** Locate the application's library folder.
524 : : *
525 : : * The path is generated by the following pseudo-code evaluation:
526 : : * \code
527 : : * prefix + "/lib"
528 : : * \endcode
529 : : *
530 : : * @param default_lib_dir A default path which will used as fallback.
531 : : * @return A string containing the library folder's path, which must be freed when
532 : : * no longer necessary. If BinReloc is not initialized, or if the
533 : : * initialization function failed, then a copy of default_lib_dir will be returned.
534 : : * If default_lib_dir is NULL, then NULL will be returned.
535 : : */
536 : : gchar *
537 : 123 : gnc_gbr_find_lib_dir (const gchar *default_lib_dir)
538 : : {
539 : 123 : return find_component_directory (default_lib_dir, LIBDIR);
540 : : }
541 : :
542 : : /** Locate the application's configuration files folder.
543 : : *
544 : : * The path is generated by the following pseudo-code evaluation:
545 : : * \code
546 : : * prefix + "/etc"
547 : : * \endcode
548 : : *
549 : : * @param default_etc_dir A default path which will used as fallback.
550 : : * @return A string containing the etc folder's path, which must be freed when
551 : : * no longer necessary. If BinReloc is not initialized, or if the initialization
552 : : * function failed, then a copy of default_etc_dir will be returned.
553 : : * If default_etc_dir is NULL, then NULL will be returned.
554 : : */
555 : : gchar *
556 : 2 : gnc_gbr_find_etc_dir (const gchar *default_etc_dir)
557 : : {
558 : 2 : return find_component_directory (default_etc_dir, SYSCONFDIR);
559 : : }
560 : :
561 : :
562 : : G_END_DECLS
563 : :
564 : : #endif /* __BINRELOC_C__ */
|