1/*
2Copyright (c) 2002 Jorge Acereda <jacereda@users.sourceforge.net>
3
4
5Permission is hereby granted, free of charge, to any person obtaining
6a copy of this software and associated documentation files (the
7"Software"), to deal in the Software without restriction, including
8without limitation the rights to use, copy, modify, merge, publish,
9distribute, sublicense, and/or sell copies of the Software, and to
10permit persons to whom the Software is furnished to do so, subject to
11the following conditions:
12
13The above copyright notice and this permission notice shall be
14included in all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23*/
24
25static void * dlopen(const char *path, int mode);
26static void * dlsym(void * handle, const char *symbol);
27static const char * dlerror(void);
28static int dlclose(void * handle);
29
30#define RTLD_LAZY	0x1
31#define RTLD_NOW	0x2
32#define RTLD_LOCAL	0x4
33#define RTLD_GLOBAL	0x8
34#define RTLD_NOLOAD	0x10
35#define RTLD_NODELETE	0x80
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <stdarg.h>
43#include <limits.h>
44#include <mach-o/dyld.h>
45
46/* Define this to make dlcompat reuse data block. This way in theory we save
47 * a little bit of overhead. However we then couldn't correctly catch excess
48 * calls to dlclose(). Hence we don't use this feature
49 */
50#undef REUSE_STATUS
51
52/* Size of the internal error message buffer (used by dlerror()) */
53#define ERR_STR_LEN			256
54
55/* Maximum number of search paths supported by getSearchPath */
56#define MAX_SEARCH_PATHS	32
57
58
59#define MAGIC_DYLIB_OFI ((NSObjectFileImage) 'DYOF')
60#define MAGIC_DYLIB_MOD ((NSModule) 'DYMO')
61
62/* internal flags */
63#define DL_IN_LIST 0x01
64
65/* This is our central data structure. Whenever a module is loaded via
66 * dlopen(), we create such a struct.
67 */
68struct dlstatus
69{
70	struct dlstatus * next;	/* pointer to next element in the linked list */
71	NSModule module;
72	const struct mach_header *lib;
73	int refs;				/* reference count */
74	int mode;				/* mode in which this module was loaded */
75	dev_t device;
76	ino_t inode;
77	int flags; 				/* Any internal flags we may need */
78};
79
80/* Head node of the dlstatus list */
81static struct dlstatus mainStatus =
82	{ 0, MAGIC_DYLIB_MOD,NULL, -1, RTLD_GLOBAL, 0, 0 };
83static struct dlstatus * stqueue = &mainStatus;
84
85
86/* Storage for the last error message (used by dlerror()) */
87static char err_str[ERR_STR_LEN];
88static int err_filled = 0;
89
90
91/* Prototypes to internal functions */
92static void debug(const char * fmt, ...);
93static void error(const char * str, ...);
94static const char * safegetenv(const char * s);
95static const char * searchList(void);
96static const char * getSearchPath(int i);
97static const char * getFullPath(int i, const char * file);
98static const struct stat * findFile(const char * file, const char ** fullPath);
99static int isValidStatus(struct dlstatus * status);
100static int isFlagSet(int mode, int flag);
101static struct dlstatus * lookupStatus(const struct stat * sbuf);
102static void insertStatus(struct dlstatus * dls, const struct stat * sbuf);
103static int promoteLocalToGlobal(struct dlstatus * dls);
104static void * reference (struct dlstatus * dls, int mode);
105static void * dlsymIntern(struct dlstatus * dls, const char *symbol, int canSetError);
106static struct dlstatus * allocStatus(void);
107static struct dlstatus * loadModule(const char * path, const struct stat * sbuf, int mode);
108
109/* Functions */
110
111#ifdef MZ_PRECISE_GC
112START_XFORM_SKIP;
113#endif
114
115static void debug(const char * fmt, ...)
116{
117#if DEBUG > 1
118	va_list arg;
119	va_start(arg, fmt);
120	fprintf(stderr, "DLDEBUG: ");
121	vfprintf(stderr, fmt, arg);
122	fprintf(stderr, "\n");
123	fflush(stderr);
124	va_end(arg);
125#endif
126}
127
128static void error(const char * str, ...)
129{
130	va_list arg;
131	va_start(arg, str);
132	strncpy(err_str, "dlcompat: ", ERR_STR_LEN);
133	vsnprintf(err_str+10, ERR_STR_LEN-10, str, arg);
134	va_end(arg);
135	debug("ERROR: %s\n", err_str);
136	err_filled = 1;
137}
138
139static void warning(const char * str)
140{
141#if DEBUG > 0
142	fprintf(stderr, "WARNING: dlcompat: %s\n", str);
143#endif
144}
145
146static const char * safegetenv(const char * s)
147{
148	const char * ss = getenv(s);
149	return ss? ss : "";
150}
151
152/* Compute and return a list of all directories that we should search when
153 * trying to locate a module. We first look at the values of LD_LIBRARY_PATH
154 * and DYLD_LIBRARY_PATH, and then finally fall back to looking into
155 * /usr/lib and /lib. Since both of the environments variables can contain a
156 * list of colon separated paths, we simply concat them and the two other paths
157 * into one big string, which we then can easily parse.
158 * Splitting this string into the actual path list is done by getSearchPath()
159 */
160static const char * searchList()
161{
162	char * buf;
163	const char * ldlp = safegetenv("LD_LIBRARY_PATH");
164	const char * dyldlp = safegetenv("DYLD_LIBRARY_PATH");
165	size_t	buf_size = strlen(ldlp) + strlen(dyldlp) + 20;
166	buf = malloc(buf_size);
167	snprintf(buf, buf_size, "%s:%s:/usr/lib:/lib", dyldlp, ldlp);
168	return buf;
169}
170
171/* Returns the ith search path from the list as computed by searchList() */
172static const char * getSearchPath(int i)
173{
174	static const char * list = 0;
175	static const char * path[MAX_SEARCH_PATHS] = {0};
176	static int end = 0;
177	if (!list && !end)
178		list = searchList();
179	while (!path[i] && !end)
180	{
181		path[i] = strsep((char**)&list, ":");
182		if (path[i][0] == 0)
183			path[i] = 0;
184		end = list == 0;
185	}
186	return path[i];
187}
188
189static const char * getFullPath(int i, const char * file)
190{
191	static char buf[PATH_MAX];
192	const char * path = getSearchPath(i);
193	if (path)
194		snprintf(buf, PATH_MAX, "%s/%s", path, file);
195	return path? buf : 0;
196}
197
198/* Given a file name, try to determine the full path for that file. Starts
199 * its search in the current directory, and then tries all paths in the
200 * search list in the order they are specified there.
201 */
202static const struct stat * findFile(const char * file, const char ** fullPath)
203{
204	int i = 0;
205	static struct stat sbuf;
206	debug("finding file %s",file);
207	*fullPath = file;
208	do
209	{
210		if (0 == stat(*fullPath, &sbuf))
211			return &sbuf;
212	}
213	while ((*fullPath = getFullPath(i++, file)));
214	return 0;
215}
216
217/* Determine whether a given dlstatus is valid or not */
218static int isValidStatus(struct dlstatus * status)
219{
220	/* Walk the list to verify status is contained in it */
221	struct dlstatus * dls = stqueue;
222	while (dls && status != dls)
223		dls = dls->next;
224
225	if (dls == 0)
226		error("invalid handle");
227	else if (dls->module == 0)
228		error("handle to closed library");
229	else
230		return TRUE;
231	return FALSE;
232}
233
234static int isFlagSet(int mode, int flag)
235{
236	return (mode & flag) == flag;
237}
238
239static struct dlstatus * lookupStatus(const struct stat * sbuf)
240{
241	struct dlstatus * dls = stqueue;
242	debug("looking for status");
243	while (dls && (	 /* isFlagSet(dls->mode, RTLD_UNSHARED) */ 0
244			|| sbuf->st_dev != dls->device
245			|| sbuf->st_ino != dls->inode) && dls->refs)
246		dls = dls->next;
247	return dls;
248}
249
250static void insertStatus(struct dlstatus * dls, const struct stat * sbuf)
251{
252	debug("inserting status");
253	dls->inode = sbuf->st_ino;
254	dls->device = sbuf->st_dev;
255	dls->refs = 0;
256	dls->mode = 0;
257	if ((dls->flags & DL_IN_LIST) == 0) {
258		dls->next = stqueue;
259		stqueue = dls;
260		dls->flags |= DL_IN_LIST;
261	}
262}
263
264static struct dlstatus * allocStatus()
265{
266	struct dlstatus * dls;
267#ifdef REUSE_STATUS
268	dls = stqueue;
269	while (dls && dls->module)
270		dls = dls->next;
271	if (!dls)
272#endif
273	dls = malloc(sizeof(*dls));
274	dls->flags = 0;
275	return dls;
276}
277
278static int promoteLocalToGlobal(struct dlstatus * dls)
279{
280	static int (*p)(NSModule module) = 0;
281	debug("promoting");
282	if(!p)
283		_dyld_func_lookup("__dyld_NSMakePrivateModulePublic",
284			  (unsigned long *)&p);
285	return (dls->module == MAGIC_DYLIB_MOD) || (p && p(dls->module));
286}
287
288static void * reference (struct dlstatus * dls, int mode)
289{
290	if (dls)
291	{
292		if (dls->module == MAGIC_DYLIB_MOD && isFlagSet(mode, RTLD_LOCAL))
293		{
294			warning("trying to open a .dylib with RTLD_LOCAL");
295			error("unable to open a .dylib with RTLD_LOCAL");
296			return NULL;
297		}
298		if (isFlagSet(mode, RTLD_GLOBAL) &&
299			!isFlagSet(dls->mode, RTLD_GLOBAL) &&
300			!promoteLocalToGlobal(dls))
301		{
302			error("unable to promote local module to global");
303			return NULL;
304		}
305		dls->mode |= mode;
306		dls->refs++;
307	}
308	else
309		debug("reference called with NULL argument");
310
311	return dls;
312}
313
314static void * dlsymIntern(struct dlstatus * dls, const char *symbol,int canSetError)
315{
316	NSSymbol * nssym = 0;
317	/* If it is a module - use NSLookupSymbolInModule */
318	if (dls->module != MAGIC_DYLIB_MOD) {
319
320		nssym = NSLookupSymbolInModule(dls->module, symbol);
321		if (!nssym && canSetError)
322			error("unable to find symbol \"%s\"", symbol);
323	}
324	else {
325		if (dls->lib != NULL) {
326			/* dylib, use NSIsSymbolNameDefinedInImage */
327			if (NSIsSymbolNameDefinedInImage(dls->lib,symbol)) {
328				nssym = NSLookupSymbolInImage(dls->lib,
329								  symbol,
330								  NSLOOKUPSYMBOLINIMAGE_OPTION_BIND
331								  | NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR
332								  );
333			}
334		}
335		else {
336			/* Global context, use NSLookupAndBindSymbol */
337			nssym = NSLookupAndBindSymbol(symbol);
338		}
339		if (!nssym) {
340			NSLinkEditErrors ler;
341			int lerno;
342			const char* errstr;
343			const char* file;
344			NSLinkEditError(&ler,&lerno,&file,&errstr);
345			if (errstr && strlen(errstr)) {
346				if (canSetError)
347					error(errstr);
348				debug("%s",errstr);
349			}
350
351		}
352	}
353	if (!nssym)
354		return NULL;
355	return NSAddressOfSymbol(nssym);
356}
357
358static struct dlstatus * loadModule(const char * path,
359					const struct stat * sbuf, int mode)
360{
361	NSObjectFileImage ofi = 0;
362	NSObjectFileImageReturnCode ofirc;
363	struct dlstatus * dls;
364	NSLinkEditErrors ler;
365	int lerno;
366	const char* errstr;
367	const char* file;
368	void (*init)(void);
369
370	ofirc = NSCreateObjectFileImageFromFile(path, &ofi);
371	switch (ofirc)
372	{
373		case NSObjectFileImageSuccess:
374			break;
375		case NSObjectFileImageInappropriateFile:
376			if (isFlagSet(mode, RTLD_LOCAL))
377			{
378				warning("trying to open a .dylib with RTLD_LOCAL");
379				error("unable to open this file with RTLD_LOCAL");
380				return NULL;
381			}
382			break;
383		case NSObjectFileImageFailure:
384			error("object file setup failure");
385			return NULL;
386		case NSObjectFileImageArch:
387			error("no object for this architecture");
388			return NULL;
389		case NSObjectFileImageFormat:
390			error("bad object file format");
391			return NULL;
392		case NSObjectFileImageAccess:
393			error("can't read object file");
394			return NULL;
395		default:
396			error("unknown error from NSCreateObjectFileImageFromFile()");
397			return NULL;
398	}
399	dls = lookupStatus(sbuf);
400	if (!dls) {
401		dls = allocStatus();
402	}
403	if (!dls)
404	{
405		error("unable to allocate memory");
406		return NULL;
407	}
408	dls->lib=0;
409	if (ofirc == NSObjectFileImageInappropriateFile)
410	{
411		if ((dls->lib = NSAddImage(path, NSADDIMAGE_OPTION_RETURN_ON_ERROR)))
412		{
413			debug("Dynamic lib loaded at %ld",dls->lib);
414			ofi = MAGIC_DYLIB_OFI;
415			dls->module = MAGIC_DYLIB_MOD;
416			ofirc = NSObjectFileImageSuccess;
417		}
418	}
419	else
420	{
421		/* Should change this to take care of RLTD_LAZY etc */
422		dls->module = NSLinkModule(ofi, path,
423					NSLINKMODULE_OPTION_RETURN_ON_ERROR
424					 | NSLINKMODULE_OPTION_PRIVATE
425					| NSLINKMODULE_OPTION_BINDNOW);
426		NSDestroyObjectFileImage(ofi);
427	}
428	if (!dls->module)
429	{
430		NSLinkEditError(&ler,&lerno,&file,&errstr);
431		free(dls);
432		error(errstr);
433		return NULL;
434	}
435	insertStatus(dls, sbuf);
436
437	if ((init = dlsymIntern(dls, "__init",0)))
438	{
439		debug("calling _init()");
440		init();
441	}
442
443	return dls;
444}
445
446static void * dlopen(const char * path, int mode)
447{
448	const struct stat * sbuf;
449	struct dlstatus * dls;
450	const char * fullPath;
451	if (!path)
452	{
453		return &mainStatus;
454	}
455	if (!(sbuf = findFile(path, &fullPath)))
456	{
457		error("file \"%s\" not found", path);
458		return NULL;
459	}
460	/* Now checks that it hasn't been closed already */
461	if ((dls = lookupStatus(sbuf)) && (dls->refs > 0))
462	{
463		/* debug("status found"); */
464		return reference(dls, mode);
465	}
466	if (isFlagSet(mode, RTLD_NOLOAD))
467	{
468		error("no existing handle and RTLD_NOLOAD specified");
469		return NULL;
470	}
471	return reference(loadModule(fullPath, sbuf, mode),mode);
472}
473
474static void * dlsym(void * handle, const char *symbol)
475{
476	struct dlstatus * dls = handle;
477	void * addr = 0;
478
479	if (!isValidStatus(dls))
480		return NULL;
481	addr = dlsymIntern(dls, symbol,1);
482	return addr;
483}
484
485static int dlclose(void * handle)
486{
487	struct dlstatus * dls = handle;
488	if (!isValidStatus(dls))
489		return 1;
490	if (dls->module == MAGIC_DYLIB_MOD)
491	{
492		warning("trying to close a .dylib!");
493		error("dynamic libraries cannot be closed");
494		return 1;
495	}
496	if (!dls->module)
497	{
498		error("module already closed");
499		return 1;
500	}
501	dls->refs--;
502	if (!dls->refs)
503	{
504		unsigned long options = 0;
505		void (*fini)(void);
506		if ((fini = dlsymIntern(dls, "__fini",0)))
507		{
508			debug("calling _fini()");
509			fini();
510		}
511		if (isFlagSet(dls->mode, RTLD_NODELETE))
512			options |= NSUNLINKMODULE_OPTION_KEEP_MEMORY_MAPPED;
513		if (!NSUnLinkModule(dls->module, options))
514		{
515			error("unable to unlink module");
516			return 1;
517		}
518		dls->module = 0;
519		/* Note: the dlstatus struct dls is neither removed from the list
520		 * nor is the memory it occupies freed. This shouldn't pose a
521		 * problem in mostly all cases, though.
522		 */
523	}
524	return 0;
525}
526
527static const char * dlerror(void)
528{
529	const char * e = err_filled ? err_str : 0;
530	err_filled = 0;
531	return e;
532}
533
534#ifdef MZ_PRECISE_GC
535END_XFORM_SKIP;
536#endif
537