1/*
2 * tclLoadAix.c --
3 *
4 *	This file implements the dlopen and dlsym APIs under the
5 *	AIX operating system, to enable the Tcl "load" command to
6 *	work.  This code was provided by Jens-Uwe Mager.
7 *
8 *	This file is subject to the following copyright notice, which is
9 *	different from the notice used elsewhere in Tcl.  The file has
10 *	been modified to incorporate the file dlfcn.h in-line.
11 *
12 *	Copyright (c) 1992,1993,1995,1996, Jens-Uwe Mager, Helios Software GmbH
13 *	Not derived from licensed software.
14
15 *	Permission is granted to freely use, copy, modify, and redistribute
16 *	this software, provided that the author is not construed to be liable
17 *	for any results of using the software, alterations are clearly marked
18 *	as such, and this notice is not modified.
19 *
20 * SCCS: @(#) tclLoadAix.c 1.11 96/10/07 10:41:24
21 *
22 * Note:  this file has been altered from the original in a few
23 * ways in order to work properly with Tcl.
24 */
25
26/*
27 * @(#)dlfcn.c	1.7 revision of 95/08/14  19:08:38
28 * This is an unpublished work copyright (c) 1992 HELIOS Software GmbH
29 * 30159 Hannover, Germany
30 */
31
32#include "tcl.h"
33#include "compat/dlfcn.h"
34#include <stdio.h>
35#include <errno.h>
36#include <string.h>
37#include <sys/types.h>
38#include <sys/ldr.h>
39#include <a.out.h>
40#include <ldfcn.h>
41
42/*
43 * We simulate dlopen() et al. through a call to load. Because AIX has
44 * no call to find an exported symbol we read the loader section of the
45 * loaded module and build a list of exported symbols and their virtual
46 * address.
47 */
48
49typedef struct {
50	char		*name;		/* the symbols's name */
51	void		*addr;		/* its relocated virtual address */
52} Export, *ExportPtr;
53
54/*
55 * To be able to intialize, a library may provide a dl_info structure
56 * that contains functions to be called to initialize and terminate.
57 */
58struct dl_info {
59	void (*init) (void);
60	void (*fini) (void);
61};
62
63/*
64 * The void * handle returned from dlopen is actually a ModulePtr.
65 */
66typedef struct Module {
67	struct Module	*next;
68	char		*name;		/* module name for refcounting */
69	int		refCnt;		/* the number of references */
70	void		*entry;		/* entry point from load */
71	struct dl_info	*info;		/* optional init/terminate functions */
72	struct dl_info	*cdtors;	/* optional C++ constructors */
73	int		nExports;	/* the number of exports found */
74	ExportPtr	exports;	/* the array of exports */
75} Module, *ModulePtr;
76
77/*
78 * We keep a list of all loaded modules to be able to call the fini
79 * handlers and destructors at atexit() time.
80 */
81static ModulePtr modList;
82
83/*
84 * The last error from one of the dl* routines is kept in static
85 * variables here. Each error is returned only once to the caller.
86 */
87static char errbuf[BUFSIZ];
88static int errvalid;
89
90static void caterr (char *);
91static int readExports (ModulePtr);
92static void terminate (void);
93static void *findMain (void);
94
95void *
96dlopen(path, mode)
97    const char *path;
98    int mode;
99{
100	register ModulePtr mp;
101	static void *mainModule;
102
103	/*
104	 * Upon the first call register a terminate handler that will
105	 * close all libraries. Also get a reference to the main module
106	 * for use with loadbind.
107	 */
108	if (!mainModule) {
109		if ((mainModule = findMain()) == NULL)
110			return NULL;
111		atexit(terminate);
112	}
113	/*
114	 * Scan the list of modules if we have the module already loaded.
115	 */
116	for (mp = modList; mp; mp = mp->next)
117		if (strcmp(mp->name, path) == 0) {
118			mp->refCnt++;
119			return (void *) mp;
120		}
121	if ((mp = (ModulePtr)calloc(1, sizeof(*mp))) == NULL) {
122		errvalid++;
123		strcpy(errbuf, "calloc: ");
124		strcat(errbuf, strerror(errno));
125		return (void *) NULL;
126	}
127	mp->name = malloc((unsigned) (strlen(path) + 1));
128	strcpy(mp->name, path);
129	/*
130	 * load should be declared load(const char *...). Thus we
131	 * cast the path to a normal char *. Ugly.
132	 */
133	if ((mp->entry = (void *)load((char *)path, L_NOAUTODEFER, NULL)) == NULL) {
134		free(mp->name);
135		free(mp);
136		errvalid++;
137		strcpy(errbuf, "dlopen: ");
138		strcat(errbuf, path);
139		strcat(errbuf, ": ");
140		/*
141		 * If AIX says the file is not executable, the error
142		 * can be further described by querying the loader about
143		 * the last error.
144		 */
145		if (errno == ENOEXEC) {
146			char *tmp[BUFSIZ/sizeof(char *)];
147			if (loadquery(L_GETMESSAGES, tmp, sizeof(tmp)) == -1)
148				strcpy(errbuf, strerror(errno));
149			else {
150				char **p;
151				for (p = tmp; *p; p++)
152					caterr(*p);
153			}
154		} else
155			strcat(errbuf, strerror(errno));
156		return (void *) NULL;
157	}
158	mp->refCnt = 1;
159	mp->next = modList;
160	modList = mp;
161	if (loadbind(0, mainModule, mp->entry) == -1) {
162		dlclose(mp);
163		errvalid++;
164		strcpy(errbuf, "loadbind: ");
165		strcat(errbuf, strerror(errno));
166		return (void *) NULL;
167	}
168	/*
169	 * If the user wants global binding, loadbind against all other
170	 * loaded modules.
171	 */
172	if (mode & RTLD_GLOBAL) {
173		register ModulePtr mp1;
174		for (mp1 = mp->next; mp1; mp1 = mp1->next)
175			if (loadbind(0, mp1->entry, mp->entry) == -1) {
176				dlclose(mp);
177				errvalid++;
178				strcpy(errbuf, "loadbind: ");
179				strcat(errbuf, strerror(errno));
180				return (void *) NULL;
181			}
182	}
183	if (readExports(mp) == -1) {
184		dlclose(mp);
185		return (void *) NULL;
186	}
187	/*
188	 * If there is a dl_info structure, call the init function.
189	 */
190	if (mp->info = (struct dl_info *)dlsym(mp, "dl_info")) {
191		if (mp->info->init)
192			(*mp->info->init)();
193	} else
194		errvalid = 0;
195	/*
196	 * If the shared object was compiled using xlC we will need
197	 * to call static constructors (and later on dlclose destructors).
198	 */
199	if (mp->cdtors = (struct dl_info *) dlsym(mp, "__cdtors")) {
200		while (mp->cdtors->init) {
201			(*mp->cdtors->init)();
202			mp->cdtors++;
203		}
204	} else
205		errvalid = 0;
206	return (void *) mp;
207}
208
209/*
210 * Attempt to decipher an AIX loader error message and append it
211 * to our static error message buffer.
212 */
213static void
214caterr(s)
215    char *s;
216{
217	register char *p = s;
218
219	while (*p >= '0' && *p <= '9')
220		p++;
221	switch(atoi(s)) {
222	case L_ERROR_TOOMANY:
223		strcat(errbuf, "to many errors");
224		break;
225	case L_ERROR_NOLIB:
226		strcat(errbuf, "can't load library");
227		strcat(errbuf, p);
228		break;
229	case L_ERROR_UNDEF:
230		strcat(errbuf, "can't find symbol");
231		strcat(errbuf, p);
232		break;
233	case L_ERROR_RLDBAD:
234		strcat(errbuf, "bad RLD");
235		strcat(errbuf, p);
236		break;
237	case L_ERROR_FORMAT:
238		strcat(errbuf, "bad exec format in");
239		strcat(errbuf, p);
240		break;
241	case L_ERROR_ERRNO:
242		strcat(errbuf, strerror(atoi(++p)));
243		break;
244	default:
245		strcat(errbuf, s);
246		break;
247	}
248}
249
250void *
251dlsym(handle, symbol)
252    void *handle;
253    const char *symbol;
254{
255	register ModulePtr mp = (ModulePtr)handle;
256	register ExportPtr ep;
257	register int i;
258
259	/*
260	 * Could speed up the search, but I assume that one assigns
261	 * the result to function pointers anyways.
262	 */
263	for (ep = mp->exports, i = mp->nExports; i; i--, ep++)
264		if (strcmp(ep->name, symbol) == 0)
265			return ep->addr;
266	errvalid++;
267	strcpy(errbuf, "dlsym: undefined symbol ");
268	strcat(errbuf, symbol);
269	return NULL;
270}
271
272char *
273dlerror()
274{
275	if (errvalid) {
276		errvalid = 0;
277		return errbuf;
278	}
279	return NULL;
280}
281
282int
283dlclose(handle)
284    void *handle;
285{
286	register ModulePtr mp = (ModulePtr)handle;
287	int result;
288	register ModulePtr mp1;
289
290	if (--mp->refCnt > 0)
291		return 0;
292	if (mp->info && mp->info->fini)
293		(*mp->info->fini)();
294	if (mp->cdtors)
295		while (mp->cdtors->fini) {
296			(*mp->cdtors->fini)();
297			mp->cdtors++;
298		}
299	result = unload(mp->entry);
300	if (result == -1) {
301		errvalid++;
302		strcpy(errbuf, strerror(errno));
303	}
304	if (mp->exports) {
305		register ExportPtr ep;
306		register int i;
307		for (ep = mp->exports, i = mp->nExports; i; i--, ep++)
308			if (ep->name)
309				free(ep->name);
310		free(mp->exports);
311	}
312	if (mp == modList)
313		modList = mp->next;
314	else {
315		for (mp1 = modList; mp1; mp1 = mp1->next)
316			if (mp1->next == mp) {
317				mp1->next = mp->next;
318				break;
319			}
320	}
321	free(mp->name);
322	free(mp);
323	return result;
324}
325
326static void
327terminate()
328{
329	while (modList)
330		dlclose(modList);
331}
332
333/*
334 * Build the export table from the XCOFF .loader section.
335 */
336static int
337readExports(mp)
338    ModulePtr mp;
339{
340	LDFILE *ldp = NULL;
341	SCNHDR sh, shdata;
342	LDHDR *lhp;
343	char *ldbuf;
344	LDSYM *ls;
345	int i;
346	ExportPtr ep;
347
348	if ((ldp = ldopen(mp->name, ldp)) == NULL) {
349		struct ld_info *lp;
350		char *buf;
351		int size = 4*1024;
352		if (errno != ENOENT) {
353			errvalid++;
354			strcpy(errbuf, "readExports: ");
355			strcat(errbuf, strerror(errno));
356			return -1;
357		}
358		/*
359		 * The module might be loaded due to the LIBPATH
360		 * environment variable. Search for the loaded
361		 * module using L_GETINFO.
362		 */
363		if ((buf = malloc(size)) == NULL) {
364			errvalid++;
365			strcpy(errbuf, "readExports: ");
366			strcat(errbuf, strerror(errno));
367			return -1;
368		}
369		while ((i = loadquery(L_GETINFO, buf, size)) == -1 && errno == ENOMEM) {
370			free(buf);
371			size += 4*1024;
372			if ((buf = malloc(size)) == NULL) {
373				errvalid++;
374				strcpy(errbuf, "readExports: ");
375				strcat(errbuf, strerror(errno));
376				return -1;
377			}
378		}
379		if (i == -1) {
380			errvalid++;
381			strcpy(errbuf, "readExports: ");
382			strcat(errbuf, strerror(errno));
383			free(buf);
384			return -1;
385		}
386		/*
387		 * Traverse the list of loaded modules. The entry point
388		 * returned by load() does actually point to the data
389		 * segment origin.
390		 */
391		lp = (struct ld_info *)buf;
392		while (lp) {
393			if (lp->ldinfo_dataorg == mp->entry) {
394				ldp = ldopen(lp->ldinfo_filename, ldp);
395				break;
396			}
397			if (lp->ldinfo_next == 0)
398				lp = NULL;
399			else
400				lp = (struct ld_info *)((char *)lp + lp->ldinfo_next);
401		}
402		free(buf);
403		if (!ldp) {
404			errvalid++;
405			strcpy(errbuf, "readExports: ");
406			strcat(errbuf, strerror(errno));
407			return -1;
408		}
409	}
410	if (TYPE(ldp) != U802TOCMAGIC) {
411		errvalid++;
412		strcpy(errbuf, "readExports: bad magic");
413		while(ldclose(ldp) == FAILURE)
414			;
415		return -1;
416	}
417	/*
418	 * Get the padding for the data section. This is needed for
419	 * AIX 4.1 compilers. This is used when building the final
420	 * function pointer to the exported symbol.
421	 */
422	if (ldnshread(ldp, _DATA, &shdata) != SUCCESS) {
423		errvalid++;
424		strcpy(errbuf, "readExports: cannot read data section header");
425		while(ldclose(ldp) == FAILURE)
426			;
427		return -1;
428	}
429	if (ldnshread(ldp, _LOADER, &sh) != SUCCESS) {
430		errvalid++;
431		strcpy(errbuf, "readExports: cannot read loader section header");
432		while(ldclose(ldp) == FAILURE)
433			;
434		return -1;
435	}
436	/*
437	 * We read the complete loader section in one chunk, this makes
438	 * finding long symbol names residing in the string table easier.
439	 */
440	if ((ldbuf = (char *)malloc(sh.s_size)) == NULL) {
441		errvalid++;
442		strcpy(errbuf, "readExports: ");
443		strcat(errbuf, strerror(errno));
444		while(ldclose(ldp) == FAILURE)
445			;
446		return -1;
447	}
448	if (FSEEK(ldp, sh.s_scnptr, BEGINNING) != OKFSEEK) {
449		errvalid++;
450		strcpy(errbuf, "readExports: cannot seek to loader section");
451		free(ldbuf);
452		while(ldclose(ldp) == FAILURE)
453			;
454		return -1;
455	}
456	if (FREAD(ldbuf, sh.s_size, 1, ldp) != 1) {
457		errvalid++;
458		strcpy(errbuf, "readExports: cannot read loader section");
459		free(ldbuf);
460		while(ldclose(ldp) == FAILURE)
461			;
462		return -1;
463	}
464	lhp = (LDHDR *)ldbuf;
465	ls = (LDSYM *)(ldbuf+LDHDRSZ);
466	/*
467	 * Count the number of exports to include in our export table.
468	 */
469	for (i = lhp->l_nsyms; i; i--, ls++) {
470		if (!LDR_EXPORT(*ls))
471			continue;
472		mp->nExports++;
473	}
474	if ((mp->exports = (ExportPtr)calloc(mp->nExports, sizeof(*mp->exports))) == NULL) {
475		errvalid++;
476		strcpy(errbuf, "readExports: ");
477		strcat(errbuf, strerror(errno));
478		free(ldbuf);
479		while(ldclose(ldp) == FAILURE)
480			;
481		return -1;
482	}
483	/*
484	 * Fill in the export table. All entries are relative to
485	 * the entry point we got from load.
486	 */
487	ep = mp->exports;
488	ls = (LDSYM *)(ldbuf+LDHDRSZ);
489	for (i = lhp->l_nsyms; i; i--, ls++) {
490		char *symname;
491		char tmpsym[SYMNMLEN+1];
492		if (!LDR_EXPORT(*ls))
493			continue;
494		if (ls->l_zeroes == 0)
495			symname = ls->l_offset+lhp->l_stoff+ldbuf;
496		else {
497			/*
498			 * The l_name member is not zero terminated, we
499			 * must copy the first SYMNMLEN chars and make
500			 * sure we have a zero byte at the end.
501			 */
502			strncpy(tmpsym, ls->l_name, SYMNMLEN);
503			tmpsym[SYMNMLEN] = '\0';
504			symname = tmpsym;
505		}
506		ep->name = malloc((unsigned) (strlen(symname) + 1));
507		strcpy(ep->name, symname);
508		ep->addr = (void *)((unsigned long)mp->entry +
509					ls->l_value - shdata.s_vaddr);
510		ep++;
511	}
512	free(ldbuf);
513	while(ldclose(ldp) == FAILURE)
514		;
515	return 0;
516}
517
518/*
519 * Find the main modules entry point. This is used as export pointer
520 * for loadbind() to be able to resolve references to the main part.
521 */
522static void *
523findMain()
524{
525	struct ld_info *lp;
526	char *buf;
527	int size = 4*1024;
528	int i;
529	void *ret;
530
531	if ((buf = malloc(size)) == NULL) {
532		errvalid++;
533		strcpy(errbuf, "findMain: ");
534		strcat(errbuf, strerror(errno));
535		return NULL;
536	}
537	while ((i = loadquery(L_GETINFO, buf, size)) == -1 && errno == ENOMEM) {
538		free(buf);
539		size += 4*1024;
540		if ((buf = malloc(size)) == NULL) {
541			errvalid++;
542			strcpy(errbuf, "findMain: ");
543			strcat(errbuf, strerror(errno));
544			return NULL;
545		}
546	}
547	if (i == -1) {
548		errvalid++;
549		strcpy(errbuf, "findMain: ");
550		strcat(errbuf, strerror(errno));
551		free(buf);
552		return NULL;
553	}
554	/*
555	 * The first entry is the main module. The entry point
556	 * returned by load() does actually point to the data
557	 * segment origin.
558	 */
559	lp = (struct ld_info *)buf;
560	ret = lp->ldinfo_dataorg;
561	free(buf);
562	return ret;
563}
564
565