1/* Provide relocatable programs.
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 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#ifdef HAVE_CONFIG_H
22# include "config.h"
23#endif
24
25/* Specification.  */
26#include "progname.h"
27
28#include <stdbool.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <fcntl.h>
33#if HAVE_UNISTD_H
34# include <unistd.h>
35#endif
36#include <sys/stat.h>
37
38/* Get declaration of _NSGetExecutablePath on MacOS X 10.2 or newer.  */
39#if HAVE_MACH_O_DYLD_H
40# include <mach-o/dyld.h>
41#endif
42
43#if defined _WIN32 || defined __WIN32__
44# define WIN32_NATIVE
45#endif
46
47#if defined WIN32_NATIVE || defined __CYGWIN__
48# define WIN32_LEAN_AND_MEAN
49# include <windows.h>
50#endif
51
52#include "xreadlink.h"
53#include "canonicalize.h"
54#include "relocatable.h"
55
56#ifdef NO_XMALLOC
57# define xmalloc malloc
58# define xstrdup strdup
59#else
60# include "xalloc.h"
61#endif
62
63/* Pathname support.
64   ISSLASH(C)           tests whether C is a directory separator character.
65   IS_PATH_WITH_DIR(P)  tests whether P contains a directory specification.
66 */
67#if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
68  /* Win32, Cygwin, OS/2, DOS */
69# define ISSLASH(C) ((C) == '/' || (C) == '\\')
70# define HAS_DEVICE(P) \
71    ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) \
72     && (P)[1] == ':')
73# define IS_PATH_WITH_DIR(P) \
74    (strchr (P, '/') != NULL || strchr (P, '\\') != NULL || HAS_DEVICE (P))
75# define FILE_SYSTEM_PREFIX_LEN(P) (HAS_DEVICE (P) ? 2 : 0)
76#else
77  /* Unix */
78# define ISSLASH(C) ((C) == '/')
79# define IS_PATH_WITH_DIR(P) (strchr (P, '/') != NULL)
80# define FILE_SYSTEM_PREFIX_LEN(P) 0
81#endif
82
83#undef set_program_name
84
85
86#if ENABLE_RELOCATABLE
87
88#ifdef __linux__
89/* File descriptor of the executable.
90   (Only used to verify that we find the correct executable.)  */
91static int executable_fd = -1;
92#endif
93
94/* Tests whether a given pathname may belong to the executable.  */
95static bool
96maybe_executable (const char *filename)
97{
98  /* Woe32 lacks the access() function, but Cygwin doesn't.  */
99#if !(defined WIN32_NATIVE && !defined __CYGWIN__)
100  if (access (filename, X_OK) < 0)
101    return false;
102
103#ifdef __linux__
104  if (executable_fd >= 0)
105    {
106      /* If we already have an executable_fd, check that filename points to
107	 the same inode.  */
108      struct stat statexe;
109      struct stat statfile;
110
111      if (fstat (executable_fd, &statexe) >= 0)
112	{
113	  if (stat (filename, &statfile) < 0)
114	    return false;
115	  if (!(statfile.st_dev
116		&& statfile.st_dev == statexe.st_dev
117		&& statfile.st_ino == statexe.st_ino))
118	    return false;
119	}
120    }
121#endif
122#endif
123
124  return true;
125}
126
127/* Determine the full pathname of the current executable, freshly allocated.
128   Return NULL if unknown.
129   Guaranteed to work on Linux and Woe32.  Likely to work on the other
130   Unixes (maybe except BeOS), under most conditions.  */
131static char *
132find_executable (const char *argv0)
133{
134#if defined WIN32_NATIVE || defined __CYGWIN__
135  char location[MAX_PATH];
136  int length = GetModuleFileName (NULL, location, sizeof (location));
137  if (length < 0)
138    return NULL;
139  if (!IS_PATH_WITH_DIR (location))
140    /* Shouldn't happen.  */
141    return NULL;
142  {
143#if defined __CYGWIN__
144    /* cygwin-1.5.13 (2005-03-01) or newer would also allow a Linux-like
145       implementation: readlink of "/proc/self/exe".  But using the
146       result of the Win32 system call is simpler and is consistent with the
147       code in relocatable.c.  */
148    /* On Cygwin, we need to convert paths coming from Win32 system calls
149       to the Unix-like slashified notation.  */
150    static char location_as_posix_path[2 * MAX_PATH];
151    /* There's no error return defined for cygwin_conv_to_posix_path.
152       See cygwin-api/func-cygwin-conv-to-posix-path.html.
153       Does it overflow the buffer of expected size MAX_PATH or does it
154       truncate the path?  I don't know.  Let's catch both.  */
155    cygwin_conv_to_posix_path (location, location_as_posix_path);
156    location_as_posix_path[MAX_PATH - 1] = '\0';
157    if (strlen (location_as_posix_path) >= MAX_PATH - 1)
158      /* A sign of buffer overflow or path truncation.  */
159      return NULL;
160    /* Call canonicalize_file_name, because Cygwin supports symbolic links.  */
161    return canonicalize_file_name (location_as_posix_path);
162#else
163    return xstrdup (location);
164#endif
165  }
166#else /* Unix && !Cygwin */
167#ifdef __linux__
168  /* The executable is accessible as /proc/<pid>/exe.  In newer Linux
169     versions, also as /proc/self/exe.  Linux >= 2.1 provides a symlink
170     to the true pathname; older Linux versions give only device and ino,
171     enclosed in brackets, which we cannot use here.  */
172  {
173    char *link;
174
175    link = xreadlink ("/proc/self/exe");
176    if (link != NULL && link[0] != '[')
177      return link;
178    if (executable_fd < 0)
179      executable_fd = open ("/proc/self/exe", O_RDONLY, 0);
180
181    {
182      char buf[6+10+5];
183      sprintf (buf, "/proc/%d/exe", getpid ());
184      link = xreadlink (buf);
185      if (link != NULL && link[0] != '[')
186	return link;
187      if (executable_fd < 0)
188	executable_fd = open (buf, O_RDONLY, 0);
189    }
190  }
191#endif
192#if HAVE_MACH_O_DYLD_H && HAVE__NSGETEXECUTABLEPATH
193  /* On MacOS X 10.2 or newer, the function
194       int _NSGetExecutablePath (char *buf, unsigned long *bufsize);
195     can be used to retrieve the executable's full path.  */
196  char location[4096];
197  unsigned long length = sizeof (location);
198  if (_NSGetExecutablePath (location, &length) == 0
199      && location[0] == '/')
200    return canonicalize_file_name (location);
201#endif
202  /* Guess the executable's full path.  We assume the executable has been
203     called via execlp() or execvp() with properly set up argv[0].  The
204     login(1) convention to add a '-' prefix to argv[0] is not supported.  */
205  {
206    bool has_slash = false;
207    {
208      const char *p;
209      for (p = argv0; *p; p++)
210	if (*p == '/')
211	  {
212	    has_slash = true;
213	    break;
214	  }
215    }
216    if (!has_slash)
217      {
218	/* exec searches paths without slashes in the directory list given
219	   by $PATH.  */
220	const char *path = getenv ("PATH");
221
222	if (path != NULL)
223	  {
224	    const char *p;
225	    const char *p_next;
226
227	    for (p = path; *p; p = p_next)
228	      {
229		const char *q;
230		size_t p_len;
231		char *concat_name;
232
233		for (q = p; *q; q++)
234		  if (*q == ':')
235		    break;
236		p_len = q - p;
237		p_next = (*q == '\0' ? q : q + 1);
238
239		/* We have a path item at p, of length p_len.
240		   Now concatenate the path item and argv0.  */
241		concat_name = (char *) xmalloc (p_len + strlen (argv0) + 2);
242#ifdef NO_XMALLOC
243		if (concat_name == NULL)
244		  return NULL;
245#endif
246		if (p_len == 0)
247		  /* An empty PATH element designates the current directory.  */
248		  strcpy (concat_name, argv0);
249		else
250		  {
251		    memcpy (concat_name, p, p_len);
252		    concat_name[p_len] = '/';
253		    strcpy (concat_name + p_len + 1, argv0);
254		  }
255		if (maybe_executable (concat_name))
256		  return canonicalize_file_name (concat_name);
257		free (concat_name);
258	      }
259	  }
260	/* Not found in the PATH, assume the current directory.  */
261      }
262    /* exec treats paths containing slashes as relative to the current
263       directory.  */
264    if (maybe_executable (argv0))
265      return canonicalize_file_name (argv0);
266  }
267  /* No way to find the executable.  */
268  return NULL;
269#endif
270}
271
272/* Full pathname of executable, or NULL.  */
273static char *executable_fullname;
274
275static void
276prepare_relocate (const char *orig_installprefix, const char *orig_installdir,
277		  const char *argv0)
278{
279  const char *curr_prefix;
280
281  /* Determine the full pathname of the current executable.  */
282  executable_fullname = find_executable (argv0);
283
284  /* Determine the current installation prefix from it.  */
285  curr_prefix = compute_curr_prefix (orig_installprefix, orig_installdir,
286				     executable_fullname);
287  if (curr_prefix != NULL)
288    /* Now pass this prefix to all copies of the relocate.c source file.  */
289    set_relocation_prefix (orig_installprefix, curr_prefix);
290}
291
292/* Set program_name, based on argv[0], and original installation prefix and
293   directory, for relocatability.  */
294void
295set_program_name_and_installdir (const char *argv0,
296				 const char *orig_installprefix,
297				 const char *orig_installdir)
298{
299  const char *argv0_stripped = argv0;
300
301  /* Relocatable programs are renamed to .bin by install-reloc.  Or, more
302     generally, their suffix is changed from $exeext to .bin$exeext.
303     Remove the ".bin" here.  */
304  {
305    size_t argv0_len = strlen (argv0);
306    const size_t exeext_len = sizeof (EXEEXT) - sizeof ("");
307    if (argv0_len > 4 + exeext_len)
308      if (memcmp (argv0 + argv0_len - exeext_len - 4, ".bin", 4) == 0)
309	{
310	  if (sizeof (EXEEXT) > sizeof (""))
311	    {
312	      /* Compare using an inlined copy of c_strncasecmp(), because
313		 the filenames may have undergone a case conversion since
314		 they were packaged.  In other words, EXEEXT may be ".exe"
315		 on one system and ".EXE" on another.  */
316	      static const char exeext[] = EXEEXT;
317	      const char *s1 = argv0 + argv0_len - exeext_len;
318	      const char *s2 = exeext;
319	      for (; *s1 != '\0'; s1++, s2++)
320		{
321		  unsigned char c1 = *s1;
322		  unsigned char c2 = *s2;
323		  if ((c1 >= 'A' && c1 <= 'Z' ? c1 - 'A' + 'a' : c1)
324		      != (c2 >= 'A' && c2 <= 'Z' ? c2 - 'A' + 'a' : c2))
325		    goto done_stripping;
326		}
327	    }
328	  /* Remove ".bin" before EXEEXT or its equivalent.  */
329	  {
330	    char *shorter = (char *) xmalloc (argv0_len - 4 + 1);
331#ifdef NO_XMALLOC
332	    if (shorter != NULL)
333#endif
334	      {
335		memcpy (shorter, argv0, argv0_len - exeext_len - 4);
336		if (sizeof (EXEEXT) > sizeof (""))
337		  memcpy (shorter + argv0_len - exeext_len - 4,
338			  argv0 + argv0_len - exeext_len - 4,
339			  exeext_len);
340		shorter[argv0_len - 4] = '\0';
341		argv0_stripped = shorter;
342	      }
343	  }
344	 done_stripping: ;
345      }
346  }
347
348  set_program_name (argv0_stripped);
349
350  prepare_relocate (orig_installprefix, orig_installdir, argv0);
351}
352
353/* Return the full pathname of the current executable, based on the earlier
354   call to set_program_name_and_installdir.  Return NULL if unknown.  */
355char *
356get_full_program_name (void)
357{
358  return executable_fullname;
359}
360
361#endif
362