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