make-relative-prefix.c revision 130562
1193323Sed/* Relative (relocatable) prefix support.
2193323Sed   Copyright (C) 1987, 1989, 1992, 1993, 1994, 1995, 1996, 1997, 1998,
3193323Sed   1999, 2000, 2001, 2002 Free Software Foundation, Inc.
4193323Sed
5193323SedThis file is part of libiberty.
6193323Sed
7193323SedGCC is free software; you can redistribute it and/or modify it under
8193323Sedthe terms of the GNU General Public License as published by the Free
9193323SedSoftware Foundation; either version 2, or (at your option) any later
10193323Sedversion.
11193323Sed
12203954SrdivackyGCC is distributed in the hope that it will be useful, but WITHOUT ANY
13193323SedWARRANTY; without even the implied warranty of MERCHANTABILITY or
14193323SedFITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15193323Sedfor more details.
16193323Sed
17193323SedYou should have received a copy of the GNU General Public License
18193323Sedalong with GCC; see the file COPYING.  If not, write to the Free
19234353SdimSoftware Foundation, 59 Temple Place - Suite 330, Boston, MA
20249423Sdim02111-1307, USA.  */
21239462Sdim
22249423Sdim/*
23249423Sdim
24239462Sdim@deftypefn Extension {const char*} make_relative_prefix (const char *@var{progname}, const char *@var{bin_prefix}, const char *@var{prefix})
25243830Sdim
26193323SedGiven three paths @var{progname}, @var{bin_prefix}, @var{prefix},
27193323Sedreturn the path that is in the same position relative to
28193323Sed@var{progname}'s directory as @var{prefix} is relative to
29193323Sed@var{bin_prefix}.  That is, a string starting with the directory
30239462Sdimportion of @var{progname}, followed by a relative pathname of the
31193323Seddifference between @var{bin_prefix} and @var{prefix}.
32193323Sed
33193323SedIf @var{progname} does not contain any directory separators,
34198090Srdivacky@code{make_relative_prefix} will search @env{PATH} to find a program
35249423Sdimnamed @var{progname}.  Also, if @var{progname} is a symbolic link,
36249423Sdimthe symbolic link will be resolved.
37249423Sdim
38193323SedFor example, if @var{bin_prefix} is @code{/alpha/beta/gamma/gcc/delta},
39193323Sed@var{prefix} is @code{/alpha/beta/gamma/omega/}, and @var{progname} is
40193323Sed@code{/red/green/blue/gcc}, then this function will return
41226633Sdim@code{/red/green/blue/../../omega/}.
42226633Sdim
43193323SedThe return value is normally allocated via @code{malloc}.  If no
44193323Sedrelative prefix can be found, return @code{NULL}.
45193323Sed
46193323Sed@end deftypefn
47193323Sed
48193323Sed*/
49193323Sed
50218893Sdim#ifdef HAVE_CONFIG_H
51193323Sed#include "config.h"
52193323Sed#endif
53194612Sed
54193323Sed#ifdef HAVE_STDLIB_H
55193323Sed#include <stdlib.h>
56193323Sed#endif
57198892Srdivacky#ifdef HAVE_UNISTD_H
58193323Sed#include <unistd.h>
59193323Sed#endif
60193323Sed
61193323Sed#include <string.h>
62193323Sed
63193323Sed#include "ansidecl.h"
64193323Sed#include "libiberty.h"
65193323Sed
66193323Sed#ifndef R_OK
67218893Sdim#define R_OK 4
68218893Sdim#define W_OK 2
69218893Sdim#define X_OK 1
70218893Sdim#endif
71193323Sed
72193323Sed#ifndef DIR_SEPARATOR
73218893Sdim#  define DIR_SEPARATOR '/'
74218893Sdim#endif
75218893Sdim
76226633Sdim#if defined (_WIN32) || defined (__MSDOS__) \
77218893Sdim    || defined (__DJGPP__) || defined (__OS2__)
78218893Sdim#  define HAVE_DOS_BASED_FILE_SYSTEM
79218893Sdim#  define HAVE_HOST_EXECUTABLE_SUFFIX
80249423Sdim#  define HOST_EXECUTABLE_SUFFIX ".exe"
81249423Sdim#  ifndef DIR_SEPARATOR_2
82249423Sdim#    define DIR_SEPARATOR_2 '\\'
83249423Sdim#  endif
84249423Sdim#  define PATH_SEPARATOR ';'
85249423Sdim#else
86249423Sdim#  define PATH_SEPARATOR ':'
87194612Sed#endif
88194612Sed
89249423Sdim#ifndef DIR_SEPARATOR_2
90249423Sdim#  define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
91249423Sdim#else
92249423Sdim#  define IS_DIR_SEPARATOR(ch) \
93249423Sdim	(((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2))
94249423Sdim#endif
95249423Sdim
96249423Sdim#define DIR_UP ".."
97249423Sdim
98193323Sedstatic char *save_string PARAMS ((const char *, int));
99193323Sedstatic char **split_directories	PARAMS ((const char *, int *));
100193323Sedstatic void free_split_directories PARAMS ((char **));
101193323Sed
102193323Sedstatic char *
103218893Sdimsave_string (s, len)
104193323Sed     const char *s;
105193323Sed     int len;
106193323Sed{
107193323Sed  char *result = malloc (len + 1);
108193323Sed
109193323Sed  memcpy (result, s, len);
110193323Sed  result[len] = 0;
111193323Sed  return result;
112193323Sed}
113193323Sed
114193323Sed/* Split a filename into component directories.  */
115193323Sed
116239462Sdimstatic char **
117239462Sdimsplit_directories (name, ptr_num_dirs)
118239462Sdim     const char *name;
119239462Sdim     int *ptr_num_dirs;
120239462Sdim{
121239462Sdim  int num_dirs = 0;
122239462Sdim  char **dirs;
123239462Sdim  const char *p, *q;
124239462Sdim  int ch;
125239462Sdim
126239462Sdim  /* Count the number of directories.  Special case MSDOS disk names as part
127239462Sdim     of the initial directory.  */
128239462Sdim  p = name;
129239462Sdim#ifdef HAVE_DOS_BASED_FILE_SYSTEM
130239462Sdim  if (name[1] == ':' && IS_DIR_SEPARATOR (name[2]))
131239462Sdim    {
132239462Sdim      p += 3;
133239462Sdim      num_dirs++;
134239462Sdim    }
135239462Sdim#endif /* HAVE_DOS_BASED_FILE_SYSTEM */
136239462Sdim
137243830Sdim  while ((ch = *p++) != '\0')
138239462Sdim    {
139239462Sdim      if (IS_DIR_SEPARATOR (ch))
140239462Sdim	{
141243830Sdim	  num_dirs++;
142239462Sdim	  while (IS_DIR_SEPARATOR (*p))
143239462Sdim	    p++;
144239462Sdim	}
145239462Sdim    }
146239462Sdim
147239462Sdim  dirs = (char **) malloc (sizeof (char *) * (num_dirs + 2));
148239462Sdim  if (dirs == NULL)
149239462Sdim    return NULL;
150239462Sdim
151239462Sdim  /* Now copy the directory parts.  */
152239462Sdim  num_dirs = 0;
153239462Sdim  p = name;
154239462Sdim#ifdef HAVE_DOS_BASED_FILE_SYSTEM
155239462Sdim  if (name[1] == ':' && IS_DIR_SEPARATOR (name[2]))
156239462Sdim    {
157239462Sdim      dirs[num_dirs++] = save_string (p, 3);
158239462Sdim      if (dirs[num_dirs - 1] == NULL)
159239462Sdim	{
160239462Sdim	  free (dirs);
161239462Sdim	  return NULL;
162239462Sdim	}
163239462Sdim      p += 3;
164239462Sdim    }
165239462Sdim#endif /* HAVE_DOS_BASED_FILE_SYSTEM */
166239462Sdim
167239462Sdim  q = p;
168239462Sdim  while ((ch = *p++) != '\0')
169239462Sdim    {
170239462Sdim      if (IS_DIR_SEPARATOR (ch))
171239462Sdim	{
172239462Sdim	  while (IS_DIR_SEPARATOR (*p))
173239462Sdim	    p++;
174239462Sdim
175239462Sdim	  dirs[num_dirs++] = save_string (q, p - q);
176239462Sdim	  if (dirs[num_dirs - 1] == NULL)
177239462Sdim	    {
178239462Sdim	      dirs[num_dirs] = NULL;
179239462Sdim	      free_split_directories (dirs);
180239462Sdim	      return NULL;
181239462Sdim	    }
182243830Sdim	  q = p;
183239462Sdim	}
184239462Sdim    }
185239462Sdim
186239462Sdim  if (p - 1 - q > 0)
187239462Sdim    dirs[num_dirs++] = save_string (q, p - 1 - q);
188239462Sdim  dirs[num_dirs] = NULL;
189239462Sdim
190239462Sdim  if (dirs[num_dirs - 1] == NULL)
191239462Sdim    {
192239462Sdim      free_split_directories (dirs);
193239462Sdim      return NULL;
194239462Sdim    }
195243830Sdim
196243830Sdim  if (ptr_num_dirs)
197239462Sdim    *ptr_num_dirs = num_dirs;
198239462Sdim  return dirs;
199239462Sdim}
200239462Sdim
201239462Sdim/* Release storage held by split directories.  */
202239462Sdim
203239462Sdimstatic void
204239462Sdimfree_split_directories (dirs)
205239462Sdim     char **dirs;
206239462Sdim{
207239462Sdim  int i = 0;
208239462Sdim
209239462Sdim  while (dirs[i] != NULL)
210218893Sdim    free (dirs[i++]);
211218893Sdim
212243830Sdim  free ((char *) dirs);
213239462Sdim}
214239462Sdim
215239462Sdim/* Given three strings PROGNAME, BIN_PREFIX, PREFIX, return a string that gets
216243830Sdim   to PREFIX starting with the directory portion of PROGNAME and a relative
217239462Sdim   pathname of the difference between BIN_PREFIX and PREFIX.
218239462Sdim
219239462Sdim   For example, if BIN_PREFIX is /alpha/beta/gamma/gcc/delta, PREFIX is
220239462Sdim   /alpha/beta/gamma/omega/, and PROGNAME is /red/green/blue/gcc, then this
221239462Sdim   function will return /red/green/blue/../../omega/.
222239462Sdim
223239462Sdim   If no relative prefix can be found, return NULL.  */
224239462Sdim
225239462Sdimchar *
226239462Sdimmake_relative_prefix (progname, bin_prefix, prefix)
227239462Sdim     const char *progname;
228239462Sdim     const char *bin_prefix;
229239462Sdim     const char *prefix;
230239462Sdim{
231239462Sdim  char **prog_dirs, **bin_dirs, **prefix_dirs;
232239462Sdim  int prog_num, bin_num, prefix_num;
233239462Sdim  int i, n, common;
234239462Sdim  int needed_len;
235239462Sdim  char *ret, *ptr, *full_progname = NULL;
236239462Sdim
237239462Sdim  if (progname == NULL || bin_prefix == NULL || prefix == NULL)
238239462Sdim    return NULL;
239239462Sdim
240239462Sdim  /* If there is no full pathname, try to find the program by checking in each
241239462Sdim     of the directories specified in the PATH environment variable.  */
242239462Sdim  if (lbasename (progname) == progname)
243239462Sdim    {
244239462Sdim      char *temp;
245239462Sdim
246239462Sdim      temp = getenv ("PATH");
247239462Sdim      if (temp)
248239462Sdim	{
249239462Sdim	  char *startp, *endp, *nstore;
250239462Sdim	  size_t prefixlen = strlen (temp) + 1;
251239462Sdim	  if (prefixlen < 2)
252239462Sdim	    prefixlen = 2;
253239462Sdim
254239462Sdim	  nstore = (char *) alloca (prefixlen + strlen (progname) + 1);
255239462Sdim
256239462Sdim	  startp = endp = temp;
257239462Sdim	  while (1)
258239462Sdim	    {
259239462Sdim	      if (*endp == PATH_SEPARATOR || *endp == 0)
260239462Sdim		{
261239462Sdim		  if (endp == startp)
262239462Sdim		    {
263239462Sdim		      nstore[0] = '.';
264221345Sdim		      nstore[1] = DIR_SEPARATOR;
265221345Sdim		      nstore[2] = '\0';
266218893Sdim		    }
267218893Sdim		  else
268218893Sdim		    {
269218893Sdim		      strncpy (nstore, startp, endp - startp);
270218893Sdim		      if (! IS_DIR_SEPARATOR (endp[-1]))
271234353Sdim			{
272234353Sdim			  nstore[endp - startp] = DIR_SEPARATOR;
273218893Sdim			  nstore[endp - startp + 1] = 0;
274218893Sdim			}
275218893Sdim		      else
276218893Sdim			nstore[endp - startp] = 0;
277218893Sdim		    }
278218893Sdim		  strcat (nstore, progname);
279234353Sdim		  if (! access (nstore, X_OK)
280234353Sdim#ifdef HAVE_HOST_EXECUTABLE_SUFFIX
281234353Sdim                      || ! access (strcat (nstore, HOST_EXECUTABLE_SUFFIX), X_OK)
282234353Sdim#endif
283234353Sdim		      )
284218893Sdim		    {
285218893Sdim		      progname = nstore;
286218893Sdim		      break;
287239462Sdim		    }
288239462Sdim
289239462Sdim		  if (*endp == 0)
290243830Sdim		    break;
291218893Sdim		  endp = startp = endp + 1;
292218893Sdim		}
293218893Sdim	      else
294218893Sdim		endp++;
295226633Sdim	    }
296226633Sdim	}
297226633Sdim    }
298226633Sdim
299218893Sdim  full_progname = lrealpath (progname);
300226633Sdim  if (full_progname == NULL)
301226633Sdim    return NULL;
302226633Sdim
303226633Sdim  prog_dirs = split_directories (full_progname, &prog_num);
304226633Sdim  bin_dirs = split_directories (bin_prefix, &bin_num);
305226633Sdim  free (full_progname);
306226633Sdim  if (bin_dirs == NULL || prog_dirs == NULL)
307226633Sdim    return NULL;
308226633Sdim
309226633Sdim  /* Remove the program name from comparison of directory names.  */
310226633Sdim  prog_num--;
311226633Sdim
312226633Sdim  /* If we are still installed in the standard location, we don't need to
313226633Sdim     specify relative directories.  Also, if argv[0] still doesn't contain
314218893Sdim     any directory specifiers after the search above, then there is not much
315218893Sdim     we can do.  */
316218893Sdim  if (prog_num == bin_num)
317218893Sdim    {
318218893Sdim      for (i = 0; i < bin_num; i++)
319218893Sdim	{
320218893Sdim	  if (strcmp (prog_dirs[i], bin_dirs[i]) != 0)
321218893Sdim	    break;
322218893Sdim	}
323218893Sdim
324218893Sdim      if (prog_num <= 0 || i == bin_num)
325218893Sdim	{
326218893Sdim	  free_split_directories (prog_dirs);
327218893Sdim	  free_split_directories (bin_dirs);
328218893Sdim	  prog_dirs = bin_dirs = (char **) 0;
329221345Sdim	  return NULL;
330221345Sdim	}
331221345Sdim    }
332221345Sdim
333221345Sdim  prefix_dirs = split_directories (prefix, &prefix_num);
334221345Sdim  if (prefix_dirs == NULL)
335218893Sdim    {
336218893Sdim      free_split_directories (prog_dirs);
337218893Sdim      free_split_directories (bin_dirs);
338218893Sdim      return NULL;
339223017Sdim    }
340221345Sdim
341221345Sdim  /* Find how many directories are in common between bin_prefix & prefix.  */
342221345Sdim  n = (prefix_num < bin_num) ? prefix_num : bin_num;
343221345Sdim  for (common = 0; common < n; common++)
344221345Sdim    {
345221345Sdim      if (strcmp (bin_dirs[common], prefix_dirs[common]) != 0)
346221345Sdim	break;
347221345Sdim    }
348221345Sdim
349221345Sdim  /* If there are no common directories, there can be no relative prefix.  */
350221345Sdim  if (common == 0)
351218893Sdim    {
352218893Sdim      free_split_directories (prog_dirs);
353218893Sdim      free_split_directories (bin_dirs);
354218893Sdim      free_split_directories (prefix_dirs);
355218893Sdim      return NULL;
356218893Sdim    }
357218893Sdim
358218893Sdim  /* Two passes: first figure out the size of the result string, and
359218893Sdim     then construct it.  */
360  needed_len = 0;
361  for (i = 0; i < prog_num; i++)
362    needed_len += strlen (prog_dirs[i]);
363  needed_len += sizeof (DIR_UP) * (bin_num - common);
364  for (i = common; i < prefix_num; i++)
365    needed_len += strlen (prefix_dirs[i]);
366  needed_len += 1; /* Trailing NUL.  */
367
368  ret = (char *) malloc (needed_len);
369  if (ret == NULL)
370    return NULL;
371
372  /* Build up the pathnames in argv[0].  */
373  *ret = '\0';
374  for (i = 0; i < prog_num; i++)
375    strcat (ret, prog_dirs[i]);
376
377  /* Now build up the ..'s.  */
378  ptr = ret + strlen(ret);
379  for (i = common; i < bin_num; i++)
380    {
381      strcpy (ptr, DIR_UP);
382      ptr += sizeof (DIR_UP) - 1;
383      *(ptr++) = DIR_SEPARATOR;
384    }
385  *ptr = '\0';
386
387  /* Put in directories to move over to prefix.  */
388  for (i = common; i < prefix_num; i++)
389    strcat (ret, prefix_dirs[i]);
390
391  free_split_directories (prog_dirs);
392  free_split_directories (bin_dirs);
393  free_split_directories (prefix_dirs);
394
395  return ret;
396}
397