* src/procmime.c
[claws.git] / intl / relocatable.c
1 /* Provide relocatable packages.
2    Copyright (C) 2003 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2003.
4
5    This program is free software; you can redistribute it and/or modify it
6    under the terms of the GNU Library General Public License as published
7    by the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18    USA.  */
19
20
21 /* Tell glibc's <stdio.h> to provide a prototype for getline().
22    This must come before <config.h> because <config.h> may include
23    <features.h>, and once <features.h> has been included, it's too late.  */
24 #ifndef _GNU_SOURCE
25 # define _GNU_SOURCE    1
26 #endif
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 /* Specification.  */
33 #include "relocatable.h"
34
35 #if ENABLE_RELOCATABLE
36
37 #include <stddef.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #ifdef NO_XMALLOC
43 # define xmalloc malloc
44 #else
45 # include "xmalloc.h"
46 #endif
47
48 #if DEPENDS_ON_LIBCHARSET
49 # include <libcharset.h>
50 #endif
51 #if DEPENDS_ON_LIBICONV && HAVE_ICONV
52 # include <iconv.h>
53 #endif
54 #if DEPENDS_ON_LIBINTL && ENABLE_NLS
55 # include <libintl.h>
56 #endif
57
58 /* Faked cheap 'bool'.  */
59 #undef bool
60 #undef false
61 #undef true
62 #define bool int
63 #define false 0
64 #define true 1
65
66 /* Pathname support.
67    ISSLASH(C)           tests whether C is a directory separator character.
68    IS_PATH_WITH_DIR(P)  tests whether P contains a directory specification.
69  */
70 #if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
71   /* Win32, OS/2, DOS */
72 # define ISSLASH(C) ((C) == '/' || (C) == '\\')
73 # define HAS_DEVICE(P) \
74     ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) \
75      && (P)[1] == ':')
76 # define IS_PATH_WITH_DIR(P) \
77     (strchr (P, '/') != NULL || strchr (P, '\\') != NULL || HAS_DEVICE (P))
78 # define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE (P) ? 2 : 0)
79 #else
80   /* Unix */
81 # define ISSLASH(C) ((C) == '/')
82 # define IS_PATH_WITH_DIR(P) (strchr (P, '/') != NULL)
83 # define FILESYSTEM_PREFIX_LEN(P) 0
84 #endif
85
86 /* Original installation prefix.  */
87 static char *orig_prefix;
88 static size_t orig_prefix_len;
89 /* Current installation prefix.  */
90 static char *curr_prefix;
91 static size_t curr_prefix_len;
92 /* These prefixes do not end in a slash.  Anything that will be concatenated
93    to them must start with a slash.  */
94
95 /* Sets the original and the current installation prefix of this module.
96    Relocation simply replaces a pathname starting with the original prefix
97    by the corresponding pathname with the current prefix instead.  Both
98    prefixes should be directory names without trailing slash (i.e. use ""
99    instead of "/").  */
100 static void
101 set_this_relocation_prefix (const char *orig_prefix_arg,
102                             const char *curr_prefix_arg)
103 {
104   if (orig_prefix_arg != NULL && curr_prefix_arg != NULL
105       /* Optimization: if orig_prefix and curr_prefix are equal, the
106          relocation is a nop.  */
107       && strcmp (orig_prefix_arg, curr_prefix_arg) != 0)
108     {
109       /* Duplicate the argument strings.  */
110       char *memory;
111
112       orig_prefix_len = strlen (orig_prefix_arg);
113       curr_prefix_len = strlen (curr_prefix_arg);
114       memory = (char *) xmalloc (orig_prefix_len + 1 + curr_prefix_len + 1);
115 #ifdef NO_XMALLOC
116       if (memory != NULL)
117 #endif
118         {
119           memcpy (memory, orig_prefix_arg, orig_prefix_len + 1);
120           orig_prefix = memory;
121           memory += orig_prefix_len + 1;
122           memcpy (memory, curr_prefix_arg, curr_prefix_len + 1);
123           curr_prefix = memory;
124           return;
125         }
126     }
127   orig_prefix = NULL;
128   curr_prefix = NULL;
129   /* Don't worry about wasted memory here - this function is usually only
130      called once.  */
131 }
132
133 /* Sets the original and the current installation prefix of the package.
134    Relocation simply replaces a pathname starting with the original prefix
135    by the corresponding pathname with the current prefix instead.  Both
136    prefixes should be directory names without trailing slash (i.e. use ""
137    instead of "/").  */
138 void
139 set_relocation_prefix (const char *orig_prefix_arg, const char *curr_prefix_arg)
140 {
141   set_this_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
142
143   /* Now notify all dependent libraries.  */
144 #if DEPENDS_ON_LIBCHARSET
145   libcharset_set_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
146 #endif
147 #if DEPENDS_ON_LIBICONV && HAVE_ICONV && _LIBICONV_VERSION >= 0x0109
148   libiconv_set_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
149 #endif
150 #if DEPENDS_ON_LIBINTL && ENABLE_NLS && defined libintl_set_relocation_prefix
151   libintl_set_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
152 #endif
153 }
154
155 /* Convenience function:
156    Computes the current installation prefix, based on the original
157    installation prefix, the original installation directory of a particular
158    file, and the current pathname of this file.  Returns NULL upon failure.  */
159 #ifdef IN_LIBRARY
160 #define compute_curr_prefix local_compute_curr_prefix
161 static
162 #endif
163 const char *
164 compute_curr_prefix (const char *orig_installprefix,
165                      const char *orig_installdir,
166                      const char *curr_pathname)
167 {
168   const char *curr_installdir;
169   const char *rel_installdir;
170
171   if (curr_pathname == NULL)
172     return NULL;
173
174   /* Determine the relative installation directory, relative to the prefix.
175      This is simply the difference between orig_installprefix and
176      orig_installdir.  */
177   if (strncmp (orig_installprefix, orig_installdir, strlen (orig_installprefix))
178       != 0)
179     /* Shouldn't happen - nothing should be installed outside $(prefix).  */
180     return NULL;
181   rel_installdir = orig_installdir + strlen (orig_installprefix);
182
183   /* Determine the current installation directory.  */
184   {
185     const char *p_base = curr_pathname + FILESYSTEM_PREFIX_LEN (curr_pathname);
186     const char *p = curr_pathname + strlen (curr_pathname);
187     char *q;
188
189     while (p > p_base)
190       {
191         p--;
192         if (ISSLASH (*p))
193           break;
194       }
195
196     q = (char *) xmalloc (p - curr_pathname + 1);
197 #ifdef NO_XMALLOC
198     if (q == NULL)
199       return NULL;
200 #endif
201     memcpy (q, curr_pathname, p - curr_pathname);
202     q[p - curr_pathname] = '\0';
203     curr_installdir = q;
204   }
205
206   /* Compute the current installation prefix by removing the trailing
207      rel_installdir from it.  */
208   {
209     const char *rp = rel_installdir + strlen (rel_installdir);
210     const char *cp = curr_installdir + strlen (curr_installdir);
211     const char *cp_base =
212       curr_installdir + FILESYSTEM_PREFIX_LEN (curr_installdir);
213
214     while (rp > rel_installdir && cp > cp_base)
215       {
216         bool same = false;
217         const char *rpi = rp;
218         const char *cpi = cp;
219
220         while (rpi > rel_installdir && cpi > cp_base)
221           {
222             rpi--;
223             cpi--;
224             if (ISSLASH (*rpi) || ISSLASH (*cpi))
225               {
226                 if (ISSLASH (*rpi) && ISSLASH (*cpi))
227                   same = true;
228                 break;
229               }
230 #if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
231             /* Win32, OS/2, DOS - case insignificant filesystem */
232             if ((*rpi >= 'a' && *rpi <= 'z' ? *rpi - 'a' + 'A' : *rpi)
233                 != (*cpi >= 'a' && *cpi <= 'z' ? *cpi - 'a' + 'A' : *cpi))
234               break;
235 #else
236             if (*rpi != *cpi)
237               break;
238 #endif
239           }
240         if (!same)
241           break;
242         /* The last pathname component was the same.  opi and cpi now point
243            to the slash before it.  */
244         rp = rpi;
245         cp = cpi;
246       }
247
248     if (rp > rel_installdir)
249       /* Unexpected: The curr_installdir does not end with rel_installdir.  */
250       return NULL;
251
252     {
253       size_t curr_prefix_len = cp - curr_installdir;
254       char *curr_prefix;
255
256       curr_prefix = (char *) xmalloc (curr_prefix_len + 1);
257 #ifdef NO_XMALLOC
258       if (curr_prefix == NULL)
259         return NULL;
260 #endif
261       memcpy (curr_prefix, curr_installdir, curr_prefix_len);
262       curr_prefix[curr_prefix_len] = '\0';
263
264       return curr_prefix;
265     }
266   }
267 }
268
269 #if defined PIC && defined INSTALLDIR
270
271 /* Full pathname of shared library, or NULL.  */
272 static char *shared_library_fullname;
273
274 #if defined _WIN32 || defined __WIN32__
275
276 /* Determine the full pathname of the shared library when it is loaded.  */
277
278 BOOL WINAPI
279 DllMain (HINSTANCE module_handle, DWORD event, LPVOID reserved)
280 {
281   (void) reserved;
282
283   if (event == DLL_PROCESS_ATTACH)
284     {
285       /* The DLL is being loaded into an application's address range.  */
286       static char location[MAX_PATH];
287
288       if (!GetModuleFileName (module_handle, location, sizeof (location)))
289         /* Shouldn't happen.  */
290         return FALSE;
291
292       if (!IS_PATH_WITH_DIR (location))
293         /* Shouldn't happen.  */
294         return FALSE;
295
296       shared_library_fullname = strdup (location);
297     }
298
299   return TRUE;
300 }
301
302 #else /* Unix */
303
304 static void
305 find_shared_library_fullname ()
306 {
307 #ifdef __linux__
308   FILE *fp;
309
310   /* Open the current process' maps file.  It describes one VMA per line.  */
311   fp = fopen ("/proc/self/maps", "r");
312   if (fp)
313     {
314       unsigned long address = (unsigned long) &find_shared_library_fullname;
315       for (;;)
316         {
317           unsigned long start, end;
318           int c;
319
320           if (fscanf (fp, "%lx-%lx", &start, &end) != 2)
321             break;
322           if (address >= start && address <= end - 1)
323             {
324               /* Found it.  Now see if this line contains a filename.  */
325               while (c = getc (fp), c != EOF && c != '\n' && c != '/')
326                 continue;
327               if (c == '/')
328                 {
329                   size_t size;
330                   int len;
331
332                   ungetc (c, fp);
333                   shared_library_fullname = NULL; size = 0;
334                   len = getline (&shared_library_fullname, &size, fp);
335                   if (len >= 0)
336                     {
337                       /* Success: filled shared_library_fullname.  */
338                       if (len > 0 && shared_library_fullname[len - 1] == '\n')
339                         shared_library_fullname[len - 1] = '\0';
340                     }
341                 }
342               break;
343             }
344           while (c = getc (fp), c != EOF && c != '\n')
345             continue;
346         }
347       fclose (fp);
348     }
349 #endif
350 }
351
352 #endif /* WIN32 / Unix */
353
354 /* Return the full pathname of the current shared library.
355    Return NULL if unknown.
356    Guaranteed to work only on Linux and Woe32.  */
357 static char *
358 get_shared_library_fullname ()
359 {
360 #if !(defined _WIN32 || defined __WIN32__)
361   static bool tried_find_shared_library_fullname;
362   if (!tried_find_shared_library_fullname)
363     {
364       find_shared_library_fullname ();
365       tried_find_shared_library_fullname = true;
366     }
367 #endif
368   return shared_library_fullname;
369 }
370
371 #endif /* PIC */
372
373 /* Returns the pathname, relocated according to the current installation
374    directory.  */
375 const char *
376 relocate (const char *pathname)
377 {
378 #if defined PIC && defined INSTALLDIR
379   static int initialized;
380
381   /* Initialization code for a shared library.  */
382   if (!initialized)
383     {
384       /* At this point, orig_prefix and curr_prefix likely have already been
385          set through the main program's set_program_name_and_installdir
386          function.  This is sufficient in the case that the library has
387          initially been installed in the same orig_prefix.  But we can do
388          better, to also cover the cases that 1. it has been installed
389          in a different prefix before being moved to orig_prefix and (later)
390          to curr_prefix, 2. unlike the program, it has not moved away from
391          orig_prefix.  */
392       const char *orig_installprefix = INSTALLPREFIX;
393       const char *orig_installdir = INSTALLDIR;
394       const char *curr_prefix_better;
395
396       curr_prefix_better =
397         compute_curr_prefix (orig_installprefix, orig_installdir,
398                              get_shared_library_fullname ());
399       if (curr_prefix_better == NULL)
400         curr_prefix_better = curr_prefix;
401
402       set_relocation_prefix (orig_installprefix, curr_prefix_better);
403
404       initialized = 1;
405     }
406 #endif
407
408   /* Note: It is not necessary to perform case insensitive comparison here,
409      even for DOS-like filesystems, because the pathname argument was
410      typically created from the same Makefile variable as orig_prefix came
411      from.  */
412   if (orig_prefix != NULL && curr_prefix != NULL
413       && strncmp (pathname, orig_prefix, orig_prefix_len) == 0)
414     {
415       if (pathname[orig_prefix_len] == '\0')
416         /* pathname equals orig_prefix.  */
417         return curr_prefix;
418       if (ISSLASH (pathname[orig_prefix_len]))
419         {
420           /* pathname starts with orig_prefix.  */
421           const char *pathname_tail = &pathname[orig_prefix_len];
422           char *result =
423             (char *) xmalloc (curr_prefix_len + strlen (pathname_tail) + 1);
424
425 #ifdef NO_XMALLOC
426           if (result != NULL)
427 #endif
428             {
429               memcpy (result, curr_prefix, curr_prefix_len);
430               strcpy (result + curr_prefix_len, pathname_tail);
431               return result;
432             }
433         }
434     }
435   /* Nothing to relocate.  */
436   return pathname;
437 }
438
439 #endif