1 /*
2  * paths.c - get 'known' pathnames, such as the system's library directory.
3  *
4  *   Copyright (c) 2005-2020  Shiro Kawai  <shiro@acm.org>
5  *
6  *   Redistribution and use in source and binary forms, with or without
7  *   modification, are permitted provided that the following conditions
8  *   are met:
9  *
10  *   1. Redistributions of source code must retain the above copyright
11  *      notice, this list of conditions and the following disclaimer.
12  *
13  *   2. Redistributions in binary form must reproduce the above copyright
14  *      notice, this list of conditions and the following disclaimer in the
15  *      documentation and/or other materials provided with the distribution.
16  *
17  *   3. Neither the name of the authors nor the names of its contributors
18  *      may be used to endorse or promote products derived from this
19  *      software without specific prior written permission.
20  *
21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27  *   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28  *   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29  *   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30  *   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31  *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 /* This file is used by both libgauche (included from libeval.scm) and
35  * gauche-config (included from gauche-config.c).  The latter
36  * doesn't use ScmObj, so the function works on bare C strings.
37  * Note that this also included in libextra.scm for testing, so be careful
38  * not to put any public definitions here to avoid duplicate definitions.
39  * Do not include this from other files.
40  *
41  * The includer must define two macros:
42  *   const void *PATH_ALLOC(size_t size)  - allocation routine
43  *   void PATH_ERROR(const char *fmt, ...) - print error message and exit
44  */
45 
46 #define LIBGAUCHE_BODY
47 #include <stdio.h>
48 #include <string.h>
49 #include "gauche.h"
50 
51 #if !defined(PATH_ALLOC)
52 #define PATH_ALLOC(n)  malloc(n)
53 #endif
54 #if !defined(PATH_ERROR)
errfn(const char * fmt,...)55 static void errfn(const char *fmt, ...)
56 {
57     va_list ap;
58     va_start(ap, fmt);
59     vfprintf(stderr, fmt, ap);
60     va_end(ap);
61     exit(1);
62 }
63 
64 #define PATH_ERROR(...) errfn(__VA_ARGS__)
65 #endif
66 
67 /*
68  * A couple of utilities used commoly in platform-specific routines:
69  */
70 
71 /* remove N components from path */
remove_components(const char * path,int n)72 static char *remove_components(const char *path, int n)
73 {
74     ssize_t len = strlen(path);
75     for (ssize_t i = len-1, cnt = 0; i >= 0; i--) {
76         if (path[i] == '/'
77 #if defined(GAUCHE_WINDOWS)
78             || path[i] == '\\'
79 #endif
80             ) {
81             cnt++;
82             if (cnt == n) {
83                 char *buf = PATH_ALLOC(i+1);
84                 memcpy(buf, path, i);
85                 buf[i] = '\0';
86                 return buf;
87             }
88         }
89     }
90     return NULL;
91 }
92 
93 /* remove SUFFIX from dir */
remove_suffix(char * dir,const char * suffix)94 static char *remove_suffix(char *dir, const char *suffix)
95 {
96     size_t len = strlen(dir);
97     size_t suflen = strlen(suffix);
98     if (len > suflen && strncmp(dir + len - suflen, suffix, suflen) == 0) {
99         dir[len-suflen] = '\0';
100         return dir;
101     }
102     return NULL;
103 }
104 
105 /*
106  * Platform-specifc routines to obtain runtime directories.
107  *
108  * For each platform, we need the following procedures:
109  *   get_libgauche_path()  - path of the running libgauche*.
110  *   get_executable_path() - path of the running executable.
111  *   get_install_dir()    - the installation directory (the libraries
112  *                          can be found under lib/ of this directory).
113  * Those may return NULL if the directory can't be determined.
114  * Can return either static string or a string allocated by PATH_ALLOC; doesn't
115  * need to worry about freeing it.
116  * Those may throw an error with PATH_ERROR if unexpected situation occurs.
117  */
118 
119 
120 #if defined(GAUCHE_WINDOWS)
121 
get_libgauche_path()122 static const char *get_libgauche_path()
123 {
124     TCHAR path[MAX_PATH];
125     const TCHAR *libname = _T("libgauche-"GAUCHE_ABI_VERSION".dll");
126 
127     HMODULE mod = GetModuleHandle(libname);
128     if (mod == NULL) return NULL;
129     DWORD r = GetModuleFileName(mod, path, MAX_PATH);
130     if (r == 0) PATH_ERROR("GetModuleFileName failed");
131     return SCM_WCS2MBS(path);
132 }
133 
get_executable_path()134 static const char *get_executable_path()
135 {
136     TCHAR path[MAX_PATH];
137     HMODULE mod = GetModuleHandle(NULL);
138     if (mod == NULL) return NULL;
139     DWORD r = GetModuleFileName(mod, path, MAX_PATH);
140     if (r == 0) PATH_ERROR("GetModuleFileName failed");
141     return SCM_WCS2MBS(path);
142 }
143 
get_install_dir()144 static const char *get_install_dir()
145 {
146     const char * path= get_libgauche_path();
147     if (path == NULL) path = get_executable_path();
148     if (path == NULL) return NULL;
149 
150     /* On Windows, both libgauche.dll and gosh.exe are in $PREFIX\bin, and
151        libraries can be found under $PREFIX\lib.  So we have to skip
152        two directory separators. */
153     char *dir = remove_components(path, 1);
154     if (dir == NULL) return NULL;
155     char *dir1 = remove_suffix(dir, "\\bin");
156     if (dir1 != NULL) return dir1;
157     dir1 = remove_suffix(dir, "\\src"); /* while we're buliding */
158     if (dir1 != NULL) return dir1;
159     /* At this moment, we're probably in a statically linked binary.
160        We don't need a particular path, but we need something to substitute
161        '@' in the load path. */
162     return "";
163 }
164 
165 #elif defined(HAVE_LIBPROC_H) && defined(HAVE_LIBPROC)
166 
167 /* OSX */
168 #include <libproc.h>
169 
get_libgauche_path()170 static const char *get_libgauche_path()
171 {
172     return NULL;
173 }
174 
get_executable_path()175 static const char *get_executable_path()
176 {
177     pid_t selfpid = getpid();
178     char buf[PROC_PIDPATHINFO_MAXSIZE];
179     memset(buf, 0, PROC_PIDPATHINFO_MAXSIZE);
180     int r = proc_pidpath(selfpid, buf, PROC_PIDPATHINFO_MAXSIZE);
181     if (r < 0) return NULL;
182     if (r == PROC_PIDPATHINFO_MAXSIZE) return NULL; /* too long? something's funny */
183     char *path = PATH_ALLOC(r+1);
184     memcpy(path, buf, r);
185     return path;
186 }
187 
get_install_dir()188 static const char *get_install_dir()
189 {
190     const char *self = get_executable_path();
191     if (self == NULL) return NULL;
192     /* remove executable name */
193     char *dir = remove_components(self, 1);
194     if (dir == NULL) return NULL;
195     /* first, try $PREFIX/bin */
196     char *dir1 = remove_suffix(dir, "/bin");
197     if (dir1 != NULL) return dir;
198     /* next, try $PREFIX/lib/gauche-$ABI/$VERSION/$ARCH */
199     dir1 = remove_components(dir, 3);
200     if (dir1 == NULL) return NULL;
201     return remove_suffix(dir1, "/lib");
202 }
203 
204 #elif defined(GAUCHE_MACOSX_FRAMEWORK)
205 
206 #include <libgen.h>
207 #include <CoreFoundation/CoreFoundation.h>
208 
209 /* Must match the id in Info.plist */
210 #define LIBGAUCHE_ID  "com.schemearts.gauche"
211 
212 /* Subdirs appended to the bundle path */
213 #define SUBDIR   "/Versions/Current/"
214 
get_libgauche_path()215 static const char *get_libgauche_path()
216 {
217     return NULL;                /* Placeholder for now */
218 }
219 
get_executable_path()220 static const char *get_executable_path()
221 {
222     return NULL;                /* Placeholder for now */
223 }
224 
get_install_dir()225 static const char *get_install_dir()
226 {
227     CFBundleRef bundle     = NULL;
228     CFURLRef    bundleURL  = NULL;
229     CFStringRef bundlePath = NULL;
230 
231 #define CLEANUP                                 \
232     do {                                        \
233         if (bundlePath) CFRelease(bundlePath);  \
234         if (bundleURL) CFRelease(bundleURL);    \
235         if (bundle) CFRelease(bundle);          \
236     } while (0)
237 
238     bundle = CFBundleGetBundleWithIdentifier(CFSTR(LIBGAUCHE_ID));
239     if (bundle == NULL) {
240         /* This call fails when gosh is called during the build process
241            (thus, the framework hasn't been created).  For the time
242            being, we just return a dummy directory. */
243         CLEANUP;
244         return ".";
245     }
246     /* Ownership of bundle follows the Get Rule of Core Foundation.
247        ie. we must claim ownership (with the CFRetain function).
248        We are then responsible for relinquishing ownership when we
249        have finished with it. */
250     CFRetain(bundle);
251 
252     bundleURL = CFBundleCopyBundleURL(bundle);
253     if (bundleURL == NULL) {
254         CLEANUP;
255         PATH_ERROR("CFBundleCopyBundleURL failed");
256     }
257     /* Ownership of bundleURL follows the Create Rule of Core Foundation.
258        ie. it is our responsibility to relinquish ownership (using CFRelease)
259        when we have finished with it. */
260 
261     bundlePath = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle);
262     if (bundlePath == NULL) {
263         CLEANUP;
264         PATH_ERROR("CFURLCopyFileSystemPath failed");
265     }
266     /* Ownership follows the Create Rule. */
267 
268     /* Estimate string length in utf8.  This is provisional; we'll refine
269        the code later. */
270     size_t utf16len = (size_t)CFStringGetLength(bundlePath);
271     size_t maxlen = 3 * (utf16len+1)/2;
272     size_t bufsiz = maxlen + strlen(SUBDIR) + 1;
273     char* buf = PATH_ALLOC(bufsiz);
274 
275     if (!CFStringGetCString(bundlePath, buf, maxlen, kCFStringEncodingUTF8)) {
276         CLEANUP;
277         PATH_ERROR("CFStringGetCString failed");
278     }
279     strcat(buf, SUBDIR);
280     CLEANUP;
281     return buf;
282 #undef CLEANUP
283 }
284 
285 #else
286 /*
287  * The fallback case.  We try procfs, for it is supported on several platforms
288  * and doesn't use special API functions.
289  */
290 #define MAPS_LINE_MAX 4096
291 
get_libgauche_path()292 static const char *get_libgauche_path()
293 {
294     FILE *fp = fopen("/proc/self/maps", "r");
295     if (fp == NULL) return NULL;
296     const char *const libgauche = "libgauche-"GAUCHE_ABI_VERSION".so";
297 
298     char buf[MAPS_LINE_MAX+1];
299     while (fgets(buf, MAPS_LINE_MAX, fp) != NULL) {
300         const char *p = strstr(buf, libgauche);
301         if (p) {
302             const char *tail = p + strlen(libgauche);
303             for (const char *q = p-1; q >= buf; q--) {
304                 if (*q == ' ') {
305                     q++;
306                     if (q == p) {
307                         return libgauche;
308                     } else {
309                         char *r = PATH_ALLOC(tail-q+1);
310                         strncpy(r, q, tail-q);
311                         r[tail-q] = '\0';
312                         return r;
313                     }
314                 }
315             }
316         }
317     }
318     if (ferror(fp)) PATH_ERROR("Read error from /proc/self/maps");
319     return NULL;
320 }
321 
get_executable_path()322 static const char *get_executable_path()
323 {
324     const char *self = "/proc/self/exe";
325     ssize_t buflen = MAPS_LINE_MAX;
326     char *buf = PATH_ALLOC(buflen);
327     ssize_t r = readlink(self, buf, buflen);
328     if (r < 0) return NULL;     /* procfs may not be available */
329     if (r == buflen) return NULL; /* name is suspiciously long; something's wrong. */
330     buf[r] = '\0';
331     return buf;
332 }
333 
get_install_dir()334 static const char *get_install_dir()
335 {
336     /* path is either $PREFIX/lib/gauche-$ABI/$VERSION/$ARCH or $PRFIX/lib.  */
337     const char *path = get_libgauche_path();
338     if (path != NULL) {
339         /* remove libgauche-$ABI.so */
340         char *dir = remove_components(path, 1);
341         if (dir == NULL) return NULL;
342         /* first, try $PREFIX/lib. */
343         char *dir1 = remove_suffix(dir, "/lib");
344         if (dir1 != NULL) return dir1;
345         /* now we try $PREFIX/lib/gauche-$ABI/$VERSION/$ARCH */
346         dir = remove_components(path, 3);
347         if (dir != NULL) {
348             dir1 = remove_suffix(dir, "/lib");
349             if (dir1 != NULL) return dir1;
350         }
351     }
352     /* executable is in $PREFIX/lib/gauche-$ABI/$VERSION/$ARCH or $PREFIX/bin */
353     path = get_executable_path();
354     if (path != NULL) {
355         /* remove binary name */
356         char *dir = remove_components(path, 1);
357         if (dir == NULL) return NULL;
358         /* first, try $PREFIX/bin. */
359         char *dir1 = remove_suffix(dir, "/bin");
360         if (dir1 != NULL) return dir1;
361         /* now we try $PREFIX/lib/gauche-$ABI/$VERSION/$ARCH */
362         dir1 = remove_components(dir, 3);
363         if (dir1 != NULL) {
364             dir1 = remove_suffix(dir1, "/lib");
365             if (dir1 != NULL) return dir1;
366         }
367     }
368     return NULL;
369 }
370 #endif
371 
372 /*
373  * Common routines
374  */
375 
substitute_all(const char * input,const char * mark,const char * subst)376 static const char *substitute_all(const char *input,
377                                   const char *mark,
378                                   const char *subst)
379 {
380     size_t ilen = strlen(input);
381     size_t mlen = strlen(mark);
382     size_t slen = strlen(subst);
383 
384     int noccurs = 0;
385     const char *p = input;
386     const char *pend = p + ilen;
387     while (p < pend) {
388         const char *p1 = strstr(p, mark);
389         if (p1 == NULL) break;
390         noccurs++;
391         p = p1 + mlen;
392     }
393 
394     if (noccurs == 0) return input;
395     size_t buflen = noccurs * slen + ilen - noccurs * mlen;
396     char *buf = (char*)PATH_ALLOC(buflen+1);
397     char *q = buf;
398     for (p = input; noccurs > 0; noccurs--) {
399         const char *p1 = strstr(p, mark);
400         memcpy(q, p, p1-p);
401         q += p1-p;
402         memcpy(q, subst, slen);
403         q += slen;
404         p = p1 + mlen;
405     }
406     strncpy(q, p, pend-p);
407     buf[buflen] = '\0';
408     return buf;
409 }
410 
411 
412 /* The configure-generated path may have '@' in the pathnames.  We replace
413    it with the installation directory.
414 
415    NB: This is a static function, but called from gauche-config.c (it includes
416    paths.c).
417 */
replace_install_dir(const char * orig)418 static const char *replace_install_dir(const char *orig)
419 {
420     if (strstr(orig, "@") == NULL) return orig; /* no replace */
421     const char *idir =  get_install_dir();
422     if (idir == NULL) {
423         PATH_ERROR("Couldn't obtain installation directory.");
424     }
425     return substitute_all(orig, "@", idir);
426 }
427