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