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 "xalloc.h"
46#endif
47
48#if defined _WIN32 || defined __WIN32__
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 __EMX__ || defined __DJGPP__
76  /* Win32, 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 FILESYSTEM_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 FILESYSTEM_PREFIX_LEN(P) 0
89#endif
90
91/* Original installation prefix.  */
92static char *orig_prefix;
93static size_t orig_prefix_len;
94/* Current installation prefix.  */
95static char *curr_prefix;
96static 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 "/").  */
105static void
106set_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 "/").  */
143void
144set_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
168static
169#endif
170const char *
171compute_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 + FILESYSTEM_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 + FILESYSTEM_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#if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
238	    /* Win32, OS/2, DOS - case insignificant filesystem */
239	    if ((*rpi >= 'a' && *rpi <= 'z' ? *rpi - 'a' + 'A' : *rpi)
240		!= (*cpi >= 'a' && *cpi <= 'z' ? *cpi - 'a' + 'A' : *cpi))
241	      break;
242#else
243	    if (*rpi != *cpi)
244	      break;
245#endif
246	  }
247	if (!same)
248	  break;
249	/* The last pathname component was the same.  opi and cpi now point
250	   to the slash before it.  */
251	rp = rpi;
252	cp = cpi;
253      }
254
255    if (rp > rel_installdir)
256      /* Unexpected: The curr_installdir does not end with rel_installdir.  */
257      return NULL;
258
259    {
260      size_t curr_prefix_len = cp - curr_installdir;
261      char *curr_prefix;
262
263      curr_prefix = (char *) xmalloc (curr_prefix_len + 1);
264#ifdef NO_XMALLOC
265      if (curr_prefix == NULL)
266	return NULL;
267#endif
268      memcpy (curr_prefix, curr_installdir, curr_prefix_len);
269      curr_prefix[curr_prefix_len] = '\0';
270
271      return curr_prefix;
272    }
273  }
274}
275
276#endif /* !IN_LIBRARY || PIC */
277
278#if defined PIC && defined INSTALLDIR
279
280/* Full pathname of shared library, or NULL.  */
281static char *shared_library_fullname;
282
283#if defined _WIN32 || defined __WIN32__
284
285/* Determine the full pathname of the shared library when it is loaded.  */
286
287BOOL WINAPI
288DllMain (HINSTANCE module_handle, DWORD event, LPVOID reserved)
289{
290  (void) reserved;
291
292  if (event == DLL_PROCESS_ATTACH)
293    {
294      /* The DLL is being loaded into an application's address range.  */
295      static char location[MAX_PATH];
296
297      if (!GetModuleFileName (module_handle, location, sizeof (location)))
298	/* Shouldn't happen.  */
299	return FALSE;
300
301      if (!IS_PATH_WITH_DIR (location))
302	/* Shouldn't happen.  */
303	return FALSE;
304
305      shared_library_fullname = strdup (location);
306    }
307
308  return TRUE;
309}
310
311#else /* Unix */
312
313static void
314find_shared_library_fullname ()
315{
316#if defined __linux__ && __GLIBC__ >= 2
317  /* Linux has /proc/self/maps. glibc 2 has the getline() function.  */
318  FILE *fp;
319
320  /* Open the current process' maps file.  It describes one VMA per line.  */
321  fp = fopen ("/proc/self/maps", "r");
322  if (fp)
323    {
324      unsigned long address = (unsigned long) &find_shared_library_fullname;
325      for (;;)
326	{
327	  unsigned long start, end;
328	  int c;
329
330	  if (fscanf (fp, "%lx-%lx", &start, &end) != 2)
331	    break;
332	  if (address >= start && address <= end - 1)
333	    {
334	      /* Found it.  Now see if this line contains a filename.  */
335	      while (c = getc (fp), c != EOF && c != '\n' && c != '/')
336		continue;
337	      if (c == '/')
338		{
339		  size_t size;
340		  int len;
341
342		  ungetc (c, fp);
343		  shared_library_fullname = NULL; size = 0;
344		  len = getline (&shared_library_fullname, &size, fp);
345		  if (len >= 0)
346		    {
347		      /* Success: filled shared_library_fullname.  */
348		      if (len > 0 && shared_library_fullname[len - 1] == '\n')
349			shared_library_fullname[len - 1] = '\0';
350		    }
351		}
352	      break;
353	    }
354	  while (c = getc (fp), c != EOF && c != '\n')
355	    continue;
356	}
357      fclose (fp);
358    }
359#endif
360}
361
362#endif /* WIN32 / Unix */
363
364/* Return the full pathname of the current shared library.
365   Return NULL if unknown.
366   Guaranteed to work only on Linux and Woe32.  */
367static char *
368get_shared_library_fullname ()
369{
370#if !(defined _WIN32 || defined __WIN32__)
371  static bool tried_find_shared_library_fullname;
372  if (!tried_find_shared_library_fullname)
373    {
374      find_shared_library_fullname ();
375      tried_find_shared_library_fullname = true;
376    }
377#endif
378  return shared_library_fullname;
379}
380
381#endif /* PIC */
382
383/* Returns the pathname, relocated according to the current installation
384   directory.  */
385const char *
386relocate (const char *pathname)
387{
388#if defined PIC && defined INSTALLDIR
389  static int initialized;
390
391  /* Initialization code for a shared library.  */
392  if (!initialized)
393    {
394      /* At this point, orig_prefix and curr_prefix likely have already been
395	 set through the main program's set_program_name_and_installdir
396	 function.  This is sufficient in the case that the library has
397	 initially been installed in the same orig_prefix.  But we can do
398	 better, to also cover the cases that 1. it has been installed
399	 in a different prefix before being moved to orig_prefix and (later)
400	 to curr_prefix, 2. unlike the program, it has not moved away from
401	 orig_prefix.  */
402      const char *orig_installprefix = INSTALLPREFIX;
403      const char *orig_installdir = INSTALLDIR;
404      const char *curr_prefix_better;
405
406      curr_prefix_better =
407	compute_curr_prefix (orig_installprefix, orig_installdir,
408			     get_shared_library_fullname ());
409      if (curr_prefix_better == NULL)
410	curr_prefix_better = curr_prefix;
411
412      set_relocation_prefix (orig_installprefix, curr_prefix_better);
413
414      initialized = 1;
415    }
416#endif
417
418  /* Note: It is not necessary to perform case insensitive comparison here,
419     even for DOS-like filesystems, because the pathname argument was
420     typically created from the same Makefile variable as orig_prefix came
421     from.  */
422  if (orig_prefix != NULL && curr_prefix != NULL
423      && strncmp (pathname, orig_prefix, orig_prefix_len) == 0)
424    {
425      if (pathname[orig_prefix_len] == '\0')
426	/* pathname equals orig_prefix.  */
427	return curr_prefix;
428      if (ISSLASH (pathname[orig_prefix_len]))
429	{
430	  /* pathname starts with orig_prefix.  */
431	  const char *pathname_tail = &pathname[orig_prefix_len];
432	  char *result =
433	    (char *) xmalloc (curr_prefix_len + strlen (pathname_tail) + 1);
434
435#ifdef NO_XMALLOC
436	  if (result != NULL)
437#endif
438	    {
439	      memcpy (result, curr_prefix, curr_prefix_len);
440	      strcpy (result + curr_prefix_len, pathname_tail);
441	      return result;
442	    }
443	}
444    }
445  /* Nothing to relocate.  */
446  return pathname;
447}
448
449#endif
450