/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1997-2011 AT&T Intellectual Property          *
*          Copyright (c) 2020-2024 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 2.0                  *
*                                                                      *
*                A copy of the License is available at                 *
*      https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html      *
*         (with md5 checksum 84283fa8859daf213bdda5a9f8d1be1d)         *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  Martijn Dekker <martijn@inlv.org>                   *
*            Johnothan King <johnothanking@protonmail.com>             *
*                                                                      *
***********************************************************************/
/*
 * provide dlopen/dlsym/dlerror interface
 *
 * David Korn
 * Glenn Fowler
 * AT&T Research
 */

#include <ast.h>
#include <dlldefs.h>
#include <error.h>

#define T(x)	ERROR_dictionary(x)

#if _hdr_dlfcn && _lib_dlopen

	/*
	 * standard
	 */

	NoN(dlopen)

#elif _hdr_dl

	/*
	 * HP-UX
 	 */

#	include <dl.h>
#	ifndef BIND_FIRST
#	define BIND_FIRST	0x4
#	endif
#	ifndef BIND_NOSTART
#	define BIND_NOSTART	0x10
#	endif

	static shl_t	all;
	static int	err;

	extern void* dlopen(const char* path, int mode)
	{
		void*	dll;

		if (!path)
			return &all;
		if (mode)
			mode = (BIND_IMMEDIATE|BIND_FIRST|BIND_NOSTART);
		if (!(dll = shl_load(path, mode, 0L)))
			err = errno;
		return dll;
	}

	extern int dlclose(void* dll)
	{
		return 0;
	}

	extern void* dlsym(void* dll, const char* name)
	{
		shl_t	handle;
		long	addr;

		handle = dll == &all ? (shl_t)0 : (shl_t)dll;
		if (shl_findsym(&handle, name, TYPE_UNDEFINED, &addr))
		{
			err = errno;
			return NULL;
		}
		return addr;
	}

	extern char* dlerror(void)
	{
		char*	msg;

		if (!err)
			return NULL;
		msg = fmterror(err);
		err = 0;
		return msg;
	}

#elif _sys_ldr && _lib_loadbind

	/*
	 * rs6000
	 */

#	include <sys/ldr.h>
#	include <xcoff.h>

	/* xcoff module header */
	struct hdr
	{
		struct filehdr	f;
		struct aouthdr	a;
		struct scnhdr	s[1];
	};

	static struct ld_info*	ld_info;
	static unsigned int	ld_info_size = 1024;
	static void*		last_module;
	static int		err;

	extern void* dlopen(const char* path, int mode)
	{
		void*	dll;

		if (!(dll = load((char*)path, mode, getenv("LIBPATH"))))
			err = errno;
		return dll;
	}

	extern int dlclose(void* dll)
	{
		return 0;
	}

	static int getquery(void)
	{
		if (!ld_info)
			ld_info = malloc(ld_info_size);
		for (;;)
		{
			if (!ld_info)
				return 1;
			if (!loadquery(L_GETINFO, ld_info, ld_info_size))
				return 0;
			if (errno != ENOMEM)
				return 1;
			ld_info = realloc(ld_info, ld_info_size *= 2);
		}
 	}

	/* find the loaded module whose data area contains the
	 * address passed in. Remember that procedure pointers
	 * are implemented as pointers to descriptors in the
	 * data area of the module defining the procedure
	 */
	static struct ld_info* getinfo(void* module)
	{
		struct ld_info*	info = ld_info;
		int		n = 1;

		if (!ld_info || module != last_module)
		{
			last_module = module;
			if (getquery())
				return NULL;
			info = ld_info;
		}
		while (n)
		{
			if ((char*)(info->ldinfo_dataorg) <= (char*)module &&
				(char*)module <= ((char*)(info->ldinfo_dataorg)
				+ (unsigned)(info->ldinfo_datasize)))
				return info;
			if (n=info->ldinfo_next)
				info = ((char*)info + n);
		}
		return NULL;
	}

	static char* getloc(struct hdr* hdr, char* data, char* name)
	{
		struct ldhdr*	ldhdr;
		struct ldsym*	ldsym;
		ulong		datareloc;
		ulong		textreloc;
		int		i;

		/* data is relocated by the difference between
		 * its virtual origin and where it was
		 * actually placed
		 */
		/* N.B. o_sndata etc. are one based */
		datareloc = (ulong)data - hdr->s[hdr->a.o_sndata-1].s_vaddr;
		/* hdr is address of header, not text, so add text s_scnptr */
		textreloc = (ulong)hdr + hdr->s[hdr->a.o_sntext-1].s_scnptr
			- hdr->s[hdr->a.o_sntext-1].s_vaddr;
		ldhdr = ((char*)hdr+ hdr->s[hdr->a.o_snloader-1].s_scnptr);
		ldsym =  (ldhdr+1);
		/* search the exports symbols */
		for(i=0; i < ldhdr->l_nsyms;ldsym++,i++)
		{
			char *symname,symbuf[9];
			char *loc;
			/* the symbol name representation is a nuisance since
			 * 8 character names appear in l_name but may
			 * not be null-terminated. This code works around
			 * that by brute force
			 */
			if (ldsym->l_zeroes)
			{
				symname = symbuf;
				memcpy(symbuf,ldsym->l_name,8);
				symbuf[8] = 0;
			}
			else
				symname = (ldsym->l_offset + (ulong)ldhdr + ldhdr->l_stoff);
			if (strcmp(symname,name))
				continue;
			loc = (char*)ldsym->l_value;
			if ((ldsym->l_scnum==hdr->a.o_sndata) ||
				(ldsym->l_scnum==hdr->a.o_snbss))
				loc += datareloc;
			else if (ldsym->l_scnum==hdr->a.o_sntext)
				loc += textreloc;
			return loc;
		}
		return NULL;
	}

	extern void* dlsym(void* handle, const char* name)
	{
		void*		addr;
		struct ld_info*	info;

		if (!(info = getinfo(handle)) || !(addr = getloc(info->ldinfo_textorg,info->ldinfo_dataorg,(char*)name)))
		{
			err = errno;
			return NULL;
		}
		return addr;
	}

	extern char* dlerror(void)
	{
		char*	msg;

		if (!err)
			return NULL;
		msg = fmterror(err);
		err = 0;
		return msg;
	}

#elif _hdr_dll && _lib_dllload

	/*
	 * MVS
	 */

#	include <dll.h>

	static int	err;

	extern void* dlopen(const char* path, int mode)
	{
		void*	dll;

		NoP(mode);
		if (!(dll = dllload(path)))
			err = errno;
		return dll;
	}

	extern int dlclose(void* dll)
	{
		return 0;
	}

	extern void* dlsym(void* handle, const char* name)
	{
		void*	addr;

		if (!(addr = dllqueryfn(handle, (char*)name)))
			err = errno;
		return addr;
	}

	extern char* dlerror(void)
	{
		char*	msg;

		if (!err)
			return NULL;
		msg = fmterror(err);
		err = 0;
		return msg;
	}

#elif _hdr_mach_o_dyld

	/*
	 * mac[h]
	 */

#	include <mach-o/dyld.h>

	typedef const struct mach_header* NSImage;

	typedef struct Dll_s
	{
		unsigned long	magic;
		NSImage		image;
		NSModule	module;
		char		path[1];
	} Dll_t;

	#define DL_MAGIC	0x04190c04
	#define DL_NEXT		((Dll_t*)RTLD_NEXT)

	static const char*	dlmessage = "no error";

	static const char	e_cover[] = T("cannot access covered library");
	static const char	e_handle[] = T("invalid handle");
	static const char	e_nomemory[] = T("out of memory");
	static const char	e_static[] = T("image statically linked");
	static const char	e_undefined[] = T("undefined symbol");

	static Dll_t global = { DL_MAGIC };

	static void undefined(const char* name)
	{
	}

	static NSModule multiple(NSSymbol sym, NSModule om, NSModule nm)
	{
		return om;
	}

	static void linkedit(NSLinkEditErrors c, int n, const char* f, const char* m)
	{
		dlmessage = m;
	}

	static NSLinkEditErrorHandlers handlers =
	{
		undefined, multiple, linkedit
	};

	extern void* dlopen(const char* path, int mode)
	{
		Dll_t*			dll;
		int			i;
		NSObjectFileImage	image;

		static int		init = 0;

		if (!_dyld_present())
		{
			dlmessage = e_static;
			return NULL;
		}
		if (!init)
		{
			init = 1;
			NSInstallLinkEditErrorHandlers(&handlers);
		}
		if (!path)
			dll = &global;
		else if (!(dll = newof(0, Dll_t, 1, strlen(path))))
		{
			dlmessage = e_nomemory;
			return NULL;
		}
		else
		{
			switch (NSCreateObjectFileImageFromFile(path, &image))
			{
			case NSObjectFileImageSuccess:
				dll->module = NSLinkModule(image, path, (mode & RTLD_LAZY) ? 0 : NSLINKMODULE_OPTION_BINDNOW);
				NSDestroyObjectFileImage(image);
				if (!dll->module)
				{
					free(dll);
					return NULL;
				}
				break;
			case NSObjectFileImageInappropriateFile:
				dll->image = NSAddImage(path, 0);
				if (!dll->image)
				{
					free(dll);
					return NULL;
				}
				break;
			default:
				free(dll);
				return NULL;
			}
			strcpy(dll->path, path);
			dll->magic = DL_MAGIC;
		}
		return dll;
	}

	extern int dlclose(void* handle)
	{
		Dll_t*	dll = (Dll_t*)handle;

		if (!dll || dll == DL_NEXT || dll->magic != DL_MAGIC)
		{
			dlmessage = e_handle;
			return -1;
		}
		if (dll->module)
			NSUnLinkModule(dll->module, 0);
		free(dll);
		return 0;
	}

	static NSSymbol
	lookup(Dll_t* dll, const char* name)
	{
		unsigned long	pun;
		void*		address;

		if (dll == DL_NEXT)
		{
			if (!_dyld_func_lookup(name, &pun))
				return NULL;
			address = (NSSymbol)pun;
		}
		else if (dll->module)
			address = NSLookupSymbolInModule(dll->module, name);
		else if (dll->image)
		{
			if (!NSIsSymbolNameDefinedInImage(dll->image, name))
				return NULL;
			address = NSLookupSymbolInImage(dll->image, name, 0);
		}
		else
		{
			if (!NSIsSymbolNameDefined(name))
				return NULL;
			address = NSLookupAndBindSymbol(name);
		}
		if (address)
			address = NSAddressOfSymbol(address);
		return address;
	}

	extern void* dlsym(void* handle, const char* name)
	{
		Dll_t*		dll = (Dll_t*)handle;
		NSSymbol	address;
		char		buf[1024];

		if (!dll || dll != DL_NEXT && (dll->magic != DL_MAGIC || !dll->image && !dll->module))
		{
			dlmessage = e_handle;
			return NULL;
		}
		if (!(address = lookup(dll, name)) && name[0] != '_' && strlen(name) < (sizeof(buf) - 1))
		{
			buf[0] = '_';
			strcpy(buf + 1, name);
			address = lookup(dll, buf);
		}
		if (!address)
		{
			dlmessage = dll == DL_NEXT ? e_cover : e_undefined;
			return NULL;
		}
		return address;
	}

	extern char* dlerror(void)
	{
		char*	msg;

		msg = (char*)dlmessage;
		dlmessage = 0;
		return msg;
	}

#else
	/*
	 * punt
	 */

	static int	err;

	extern void* dlopen(const char* path, int mode)
	{
		err = 1;
		return NULL;
	}

	extern int dlclose(void* dll)
	{
		err = 1;
		return 0;
	}

	extern void* dlsym(void* handle, const char* name)
	{
		err = 1;
		return NULL;
	}

	extern char* dlerror(void)
	{
		if (!err)
			return NULL;
		err = 0;
		return "dynamic linking not supported";
	}

#endif
