Use m_tempfile and better errors msg
[apps/madmutt.git] / intl / relocatable.c
1 /* Provide relocatable packages.
2    Copyright (C) 2003-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
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 "xalloc.h"
46 #endif
47
48 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
49 # define WIN32_LEAN_AND_MEAN
50 # include <windows.h>
51 #endif
52
53 #if DEPENDS_ON_LIBCHARSET
54 # include <libcharset.h>
55 #endif
56 #if DEPENDS_ON_LIBICONV && HAVE_ICONV
57 # include <iconv.h>
58 #endif
59 #if DEPENDS_ON_LIBINTL && ENABLE_NLS
60 # include <libintl.h>
61 #endif
62
63 /* Faked cheap 'bool'.  */
64 #undef bool
65 #undef false
66 #undef true
67 #define bool int
68 #define false 0
69 #define true 1
70
71 /* Pathname support.
72    ISSLASH(C)           tests whether C is a directory separator character.
73    IS_PATH_WITH_DIR(P)  tests whether P contains a directory specification.
74  */
75 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
76   /* Win32, Cygwin, OS/2, DOS */
77 # define ISSLASH(C) ((C) == '/' || (C) == '\\')
78 # define HAS_DEVICE(P) \
79     ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) \
80      && (P)[1] == ':')
81 # define IS_PATH_WITH_DIR(P) \
82     (strchr (P, '/') != NULL || strchr (P, '\\') != NULL || HAS_DEVICE (P))
83 # define FILE_SYSTEM_PREFIX_LEN(P) (HAS_DEVICE (P) ? 2 : 0)
84 #else
85   /* Unix */
86 # define ISSLASH(C) ((C) == '/')
87 # define IS_PATH_WITH_DIR(P) (strchr (P, '/') != NULL)
88 # define FILE_SYSTEM_PREFIX_LEN(P) 0
89 #endif
90
91 /* Original installation prefix.  */
92 static char *orig_prefix;
93 static size_t orig_prefix_len;
94 /* Current installation prefix.  */
95 static char *curr_prefix;
96 static size_t curr_prefix_len;
97 /* These prefixes do not end in a slash.  Anything that will be concatenated
98    to them must start with a slash.  */
99
100 /* Sets the original and the current installation prefix of this module.
101    Relocation simply replaces a pathname starting with the original prefix
102    by the corresponding pathname with the current prefix instead.  Both
103    prefixes should be directory names without trailing slash (i.e. use ""
104    instead of "/").  */
105 static void
106 set_this_relocation_prefix (const char *orig_prefix_arg,
107                             const char *curr_prefix_arg)
108 {
109   if (orig_prefix_arg != NULL && curr_prefix_arg != NULL
110       /* Optimization: if orig_prefix and curr_prefix are equal, the
111          relocation is a nop.  */
112       && strcmp (orig_prefix_arg, curr_prefix_arg) != 0)
113     {
114       /* Duplicate the argument strings.  */
115       char *memory;
116
117       orig_prefix_len = strlen (orig_prefix_arg);
118       curr_prefix_len = strlen (curr_prefix_arg);
119       memory = (char *) xmalloc (orig_prefix_len + 1 + curr_prefix_len + 1);
120 #ifdef NO_XMALLOC
121       if (memory != NULL)
122 #endif
123         {
124           memcpy (memory, orig_prefix_arg, orig_prefix_len + 1);
125           orig_prefix = memory;
126           memory += orig_prefix_len + 1;
127           memcpy (memory, curr_prefix_arg, curr_prefix_len + 1);
128           curr_prefix = memory;
129           return;
130         }
131     }
132   orig_prefix = NULL;
133   curr_prefix = NULL;
134   /* Don't worry about wasted memory here - this function is usually only
135      called once.  */
136 }
137
138 /* Sets the original and the current installation prefix of the package.
139    Relocation simply replaces a pathname starting with the original prefix
140    by the corresponding pathname with the current prefix instead.  Both
141    prefixes should be directory names without trailing slash (i.e. use ""
142    instead of "/").  */
143 void
144 set_relocation_prefix (const char *orig_prefix_arg, const char *curr_prefix_arg)
145 {
146   set_this_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
147
148   /* Now notify all dependent libraries.  */
149 #if DEPENDS_ON_LIBCHARSET
150   libcharset_set_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
151 #endif
152 #if DEPENDS_ON_LIBICONV && HAVE_ICONV && _LIBICONV_VERSION >= 0x0109
153   libiconv_set_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
154 #endif
155 #if DEPENDS_ON_LIBINTL && ENABLE_NLS && defined libintl_set_relocation_prefix
156   libintl_set_relocation_prefix (orig_prefix_arg, curr_prefix_arg);
157 #endif
158 }
159
160 #if !defined IN_LIBRARY || (defined PIC && defined INSTALLDIR)
161
162 /* Convenience function:
163    Computes the current installation prefix, based on the original
164    installation prefix, the original installation directory of a particular
165    file, and the current pathname of this file.  Returns NULL upon failure.  */
166 #ifdef IN_LIBRARY
167 #define compute_curr_prefix local_compute_curr_prefix
168 static
169 #endif
170 const char *
171 compute_curr_prefix (const char *orig_installprefix,
172                      const char *orig_installdir,
173                      const char *curr_pathname)
174 {
175   const char *curr_installdir;
176   const char *rel_installdir;
177
178   if (curr_pathname == NULL)
179     return NULL;
180
181   /* Determine the relative installation directory, relative to the prefix.
182      This is simply the difference between orig_installprefix and
183      orig_installdir.  */
184   if (strncmp (orig_installprefix, orig_installdir, strlen (orig_installprefix))
185       != 0)
186     /* Shouldn't happen - nothing should be installed outside $(prefix).  */
187     return NULL;
188   rel_installdir = orig_installdir + strlen (orig_installprefix);
189
190   /* Determine the current installation directory.  */
191   {
192     const char *p_base = curr_pathname + FILE_SYSTEM_PREFIX_LEN (curr_pathname);
193     const char *p = curr_pathname + strlen (curr_pathname);
194     char *q;
195
196     while (p > p_base)
197       {
198         p--;
199         if (ISSLASH (*p))
200           break;
201       }
202
203     q = (char *) xmalloc (p - curr_pathname + 1);
204 #ifdef NO_XMALLOC
205     if (q == NULL)
206       return NULL;
207 #endif
208     memcpy (q, curr_pathname, p - curr_pathname);
209     q[p - curr_pathname] = '\0';
210     curr_installdir = q;
211   }
212
213   /* Compute the current installation prefix by removing the trailing
214      rel_installdir from it.  */
215   {
216     const char *rp = rel_installdir + strlen (rel_installdir);
217     const char *cp = curr_installdir + strlen (curr_installdir);
218     const char *cp_base =
219       curr_installdir + FILE_SYSTEM_PREFIX_LEN (curr_installdir);
220
221     while (rp > rel_installdir && cp > cp_base)
222       {
223         bool same = false;
224         const char *rpi = rp;
225         const char *cpi = cp;
226
227         while (rpi > rel_installdir && cpi > cp_base)
228           {
229             rpi--;
230             cpi--;
231             if (ISSLASH (*rpi) || ISSLASH (*cpi))
232               {
233                 if (ISSLASH (*rpi) && ISSLASH (*cpi))
234                   same = true;
235                 break;
236               }
237             /* Do case-insensitive comparison if the filesystem is always or
238                often case-insensitive.  It's better to accept the comparison
239                if the difference is only in case, rather than to fail.  */
240 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
241             /* Win32, Cygwin, OS/2, DOS - case insignificant filesystem */
242             if ((*rpi >= 'a' && *rpi <= 'z' ? *rpi - 'a' + 'A' : *rpi)
243                 != (*cpi >= 'a' && *cpi <= 'z' ? *cpi - 'a' + 'A' : *cpi))
244               break;
245 #else
246             if (*rpi != *cpi)
247               break;
248 #endif
249           }
250         if (!same)
251           break;
252         /* The last pathname component was the same.  opi and cpi now point
253            to the slash before it.  */
254         rp = rpi;
255         cp = cpi;
256       }
257
258     if (rp > rel_installdir)
259       /* Unexpected: The curr_installdir does not end with rel_installdir.  */
260       return NULL;
261
262     {
263       size_t curr_prefix_len = cp - curr_installdir;
264       char *curr_prefix;
265
266       curr_prefix = (char *) xmalloc (curr_prefix_len + 1);
267 #ifdef NO_XMALLOC
268       if (curr_prefix == NULL)
269         return NULL;
270 #endif
271       memcpy (curr_prefix, curr_installdir, curr_prefix_len);
272       curr_prefix[curr_prefix_len] = '\0';
273
274       return curr_prefix;
275     }
276   }
277 }
278
279 #endif /* !IN_LIBRARY || PIC */
280
281 #if defined PIC && defined INSTALLDIR
282
283 /* Full pathname of shared library, or NULL.  */
284 static char *shared_library_fullname;
285
286 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
287
288 /* Determine the full pathname of the shared library when it is loaded.  */
289
290 BOOL WINAPI
291 DllMain (HINSTANCE module_handle, DWORD event, LPVOID reserved)
292 {
293   (void) reserved;
294
295   if (event == DLL_PROCESS_ATTACH)
296     {
297       /* The DLL is being loaded into an application's address range.  */
298       static char location[MAX_PATH];
299
300       if (!GetModuleFileName (module_handle, location, sizeof (location)))
301         /* Shouldn't happen.  */
302         return FALSE;
303
304       if (!IS_PATH_WITH_DIR (location))
305         /* Shouldn't happen.  */
306         return FALSE;
307
308       {
309 #if defined __CYGWIN__
310         /* On Cygwin, we need to convert paths coming from Win32 system calls
311            to the Unix-like slashified notation.  */
312         static char location_as_posix_path[2 * MAX_PATH];
313         /* There's no error return defined for cygwin_conv_to_posix_path.
314            See cygwin-api/func-cygwin-conv-to-posix-path.html.
315            Does it overflow the buffer of expected size MAX_PATH or does it
316            truncate the path?  I don't know.  Let's catch both.  */
317         cygwin_conv_to_posix_path (location, location_as_posix_path);
318         location_as_posix_path[MAX_PATH - 1] = '\0';
319         if (strlen (location_as_posix_path) >= MAX_PATH - 1)
320           /* A sign of buffer overflow or path truncation.  */
321           return FALSE;
322         shared_library_fullname = strdup (location_as_posix_path);
323 #else
324         shared_library_fullname = strdup (location);
325 #endif
326       }
327     }
328
329   return TRUE;
330 }
331
332 #else /* Unix except Cygwin */
333
334 static void
335 find_shared_library_fullname ()
336 {
337 #if defined __linux__ && __GLIBC__ >= 2
338   /* Linux has /proc/self/maps. glibc 2 has the getline() function.  */
339   FILE *fp;
340
341   /* Open the current process' maps file.  It describes one VMA per line.  */
342   fp = fopen ("/proc/self/maps", "r");
343   if (fp)
344     {
345       unsigned long address = (unsigned long) &find_shared_library_fullname;
346       for (;;)
347         {
348           unsigned long start, end;
349           int c;
350
351           if (fscanf (fp, "%lx-%lx", &start, &end) != 2)
352             break;
353           if (address >= start && address <= end - 1)
354             {
355               /* Found it.  Now see if this line contains a filename.  */
356               while (c = getc (fp), c != EOF && c != '\n' && c != '/')
357                 continue;
358               if (c == '/')
359                 {
360                   size_t size;
361                   int len;
362
363                   ungetc (c, fp);
364                   shared_library_fullname = NULL; size = 0;
365                   len = getline (&shared_library_fullname, &size, fp);
366                   if (len >= 0)
367                     {
368                       /* Success: filled shared_library_fullname.  */
369                       if (len > 0 && shared_library_fullname[len - 1] == '\n')
370                         shared_library_fullname[len - 1] = '\0';
371                     }
372                 }
373               break;
374             }
375           while (c = getc (fp), c != EOF && c != '\n')
376             continue;
377         }
378       fclose (fp);
379     }
380 #endif
381 }
382
383 #endif /* (WIN32 or Cygwin) / (Unix except Cygwin) */
384
385 /* Return the full pathname of the current shared library.
386    Return NULL if unknown.
387    Guaranteed to work only on Linux, Cygwin and Woe32.  */
388 static char *
389 get_shared_library_fullname ()
390 {
391 #if !(defined _WIN32 || defined __WIN32__ || defined __CYGWIN__)
392   static bool tried_find_shared_library_fullname;
393   if (!tried_find_shared_library_fullname)
394     {
395       find_shared_library_fullname ();
396       tried_find_shared_library_fullname = true;
397     }
398 #endif
399   return shared_library_fullname;
400 }
401
402 #endif /* PIC */
403
404 /* Returns the pathname, relocated according to the current installation
405    directory.  */
406 const char *
407 relocate (const char *pathname)
408 {
409 #if defined PIC && defined INSTALLDIR
410   static int initialized;
411
412   /* Initialization code for a shared library.  */
413   if (!initialized)
414     {
415       /* At this point, orig_prefix and curr_prefix likely have already been
416          set through the main program's set_program_name_and_installdir
417          function.  This is sufficient in the case that the library has
418          initially been installed in the same orig_prefix.  But we can do
419          better, to also cover the cases that 1. it has been installed
420          in a different prefix before being moved to orig_prefix and (later)
421          to curr_prefix, 2. unlike the program, it has not moved away from
422          orig_prefix.  */
423       const char *orig_installprefix = INSTALLPREFIX;
424       const char *orig_installdir = INSTALLDIR;
425       const char *curr_prefix_better;
426
427       curr_prefix_better =
428         compute_curr_prefix (orig_installprefix, orig_installdir,
429                              get_shared_library_fullname ());
430       if (curr_prefix_better == NULL)
431         curr_prefix_better = curr_prefix;
432
433       set_relocation_prefix (orig_installprefix, curr_prefix_better);
434
435       initialized = 1;
436     }
437 #endif
438
439   /* Note: It is not necessary to perform case insensitive comparison here,
440      even for DOS-like filesystems, because the pathname argument was
441      typically created from the same Makefile variable as orig_prefix came
442      from.  */
443   if (orig_prefix != NULL && curr_prefix != NULL
444       && strncmp (pathname, orig_prefix, orig_prefix_len) == 0)
445     {
446       if (pathname[orig_prefix_len] == '\0')
447         /* pathname equals orig_prefix.  */
448         return curr_prefix;
449       if (ISSLASH (pathname[orig_prefix_len]))
450         {
451           /* pathname starts with orig_prefix.  */
452           const char *pathname_tail = &pathname[orig_prefix_len];
453           char *result =
454             (char *) xmalloc (curr_prefix_len + strlen (pathname_tail) + 1);
455
456 #ifdef NO_XMALLOC
457           if (result != NULL)
458 #endif
459             {
460               memcpy (result, curr_prefix, curr_prefix_len);
461               strcpy (result + curr_prefix_len, pathname_tail);
462               return result;
463             }
464         }
465     }
466   /* Nothing to relocate.  */
467   return pathname;
468 }
469
470 #endif