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., 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 "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.  */
87static char *orig_prefix;
88static size_t orig_prefix_len;
89/* Current installation prefix.  */
90static char *curr_prefix;
91static 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 "/").  */
100static void
101set_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 "/").  */
138void
139set_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
161static
162#endif
163const char *
164compute_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.  */
272static 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
278BOOL WINAPI
279DllMain (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
304static void
305find_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.  */
357static char *
358get_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.  */
375const char *
376relocate (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
440