1 /*
2                                     __
3                                    / _|
4   __ _ _   _ _ __ ___  _ __ __ _  | |_ ___  ___ ___
5  / _` | | | | '__/ _ \| '__/ _` | |  _/ _ \/ __/ __|
6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \
7  \__,_|\__,_|_|  \___/|_|  \__,_| |_| \___/|___/___/
8 
9 Copyright © 2013-2016, Mike Parker.
10 Copyright © 2016, 渡世白玉.
11 Copyright © 2018, Michael D. Parker
12 Copyright © 2018-2019, Aurora Free Open Source Software.
13 Copyright © 2018-2019, Luís Ferreira <luis@aurorafoss.org>
14 
15 This file is part of the Aurora Free Open Source Software. This
16 organization promote free and open source software that you can
17 redistribute and/or modify under the terms of the GNU Lesser General
18 Public License Version 3 as published by the Free Software Foundation or
19 (at your option) any later version approved by the Aurora Free Open Source
20 Software Organization. The license is available in the package root path
21 as 'LICENSE' file. Please review the following information to ensure the
22 GNU Lesser General Public License version 3 requirements will be met:
23 https://www.gnu.org/licenses/lgpl.html .
24 
25 Alternatively, this file may be used under the terms of the GNU General
26 Public License version 3 or later as published by the Free Software
27 Foundation. Please review the following information to ensure the GNU
28 General Public License requirements will be met:
29 https://www.gnu.org/licenses/gpl-3.0.html.
30 
31 NOTE: All products, services or anything associated to trademarks and
32 service marks used or referenced on this file are the property of their
33 respective companies/owners or its subsidiaries. Other names and brands
34 may be claimed as the property of others.
35 
36 For more info about intellectual property visit: aurorafoss.org or
37 directly send an email to: contact (at) aurorafoss.org .
38 
39 This file is an improvement of an existing code, part of DerelictUtil
40 from DerelictOrg. Check it out at derelictorg.github.io .
41 
42 This file is an improvement of an existing code, developed by 渡世白玉
43 and available on github at https://github.com/huntlabs/DerelictUtil .
44 
45 This file is an improvement of an existing code, part of bindbc-loader
46 from BindBC. Check it out at github.com/BindBC/bindbc-loader .
47 */
48 
49 module riverd.loader;
50 
51 /** Dynamic library loader
52  * @file riverd/loader.d
53  * @author Luís Ferreira <luis@aurorafoss.org>
54  * @author Aurora Free Open Source Software
55  * @author 渡世白玉
56  * @author Michael D. Parker
57  * @date 2013-2019
58  */
59 
60 public import riverd.builder;
61 
62 version(Posix) import core.sys.posix.dlfcn;
63 else version(Windows) import core.sys.windows.windows;
64 
65 import std.conv : to;
66 import std.traits;
67 
68 @nogc nothrow {
69 	/** Load a dynamic library
70 	 * @param name complete library string name
71 	 */
72 	void* dylib_load(const(char)* name)
73 	{
74 		version(Posix) void* handle = dlopen(name, RTLD_NOW);
75 		else version(Windows) void* handle = LoadLibraryA(name);
76 		if(handle) return handle;
77 		else return null;
78 	}
79 
80 	/** Bind a library symbol
81 	 * This function bind a specific symbol from the dynamic library.
82 	 * @param handle library handler
83 	 * @param ptr symbol pointer
84 	 * @param name symbol name
85 	 */
86 	void dylib_bindSymbol(void* handle, void** ptr, const(char)* name)
87 	{
88 		assert(handle);
89 		version(Posix) const void* sym = dlsym(handle, name);
90 		else version(Windows) const void* sym = GetProcAddress(handle, name);
91 
92 		*ptr = cast(void*)sym;
93 	}
94 
95 	/** Reports dynamic library errors
96 	 * @param buf char buffer
97 	 * @param len buffer length
98 	 */
99 	void dylib_sysError(char* buf, size_t len)
100 	{
101 		import core.stdc.string : strncpy;
102 		version(Windows) {
103 			char* msgBuf;
104 			enum uint langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
105 
106 			FormatMessageA(
107 				FORMAT_MESSAGE_ALLOCATE_BUFFER |
108 				FORMAT_MESSAGE_FROM_SYSTEM |
109 				FORMAT_MESSAGE_IGNORE_INSERTS,
110 				null,
111 				GetLastError(),
112 				langID,
113 				cast(char*)&msgBuf,
114 				0,
115 				null
116 			);
117 
118 			if(msgBuf) {
119 				strncpy(buf, msgBuf, len);
120 				buf[len - 1] = 0;
121 				LocalFree(msgBuf);
122 			}
123 			else strncpy(buf, "Unknown Error\0", len);
124 		}
125 		else version(Posix) {
126 			char* msg = dlerror();
127 			strncpy(buf, msg != null ? msg : "Unknown Error", len);
128 			buf[len - 1] = 0;
129 		}
130 	}
131 }
132 version(D_BetterC) {
133 	/** Unload a dynamic library
134 	 * @param handle dynamic library handler
135 	 */
136 	@nogc nothrow void dylib_unload(void* handle)
137 	{
138 		if(handle) {
139 			version(Posix) dlclose(handle);
140 			else version(Windows) FreeLibrary(handle);
141 			handle = null;
142 		}
143 	}
144 }
145 else {
146 	import std.array;
147 	import std.string;
148 
149 	/** Dynamic Library Version struct */
150 	struct DylibVersion
151 	{
152 		uint major; /** major version number */
153 		uint minor; /** minor version number */
154 		uint patch; /** patch version number */
155 	}
156 
157 	/** Dynamic Library Loader Exception
158 	 * This exception is thrown when the library can't be loaded
159 	 */
160 	class DylibLoadException : Exception
161 	{
162 
163 		/** Dynamic Library Loader Exception constructor
164 		 * @param msg exception message
165 		 * @param line line number in code
166 		 * @param file code file
167 	 	 */
168 		this(string msg, size_t line = __LINE__, string file = __FILE__)
169 		{
170 			super(msg, file, line, null);
171 		}
172 
173 		/** Dynamic Library Loader Exception constructor
174 		 * @param names libraries name
175 		 * @param reasons reasons why it can't load
176 		 * @param line line number in code
177 		 * @param file code file
178 	 	 */
179 		this(string[] names, string[] reasons, size_t line = __LINE__, string file = __FILE__)
180 		{
181 			string msg = "Failed to load one or more shared libraries:";
182 			foreach(i, name; names) {
183 				msg ~= "\n\t" ~ name ~ " - ";
184 				if(i < reasons.length)
185 					msg ~= reasons[i];
186 				else
187 					msg ~= "Unknown";
188 			}
189 			this(msg, line, file);
190 		}
191 
192 		/** Dynamic Library Loader Exception constructor
193 		 * @param msg exception message
194 		 * @param name library name
195 		 * @param line line number in code
196 		 * @param file code file
197 	 	 */
198 		this(string msg, string name = "", size_t line = __LINE__, string file = __FILE__)
199 		{
200 			super(msg, file, line, null);
201 			_name = name;
202 		}
203 
204 		/** Get the library name */
205 		pure nothrow @nogc
206 		@property string name()
207 		{
208 			return _name;
209 		}
210 
211 		private string _name; /** library name */
212 	}
213 
214 
215 	class DylibSymbolLoadException : Exception
216 	{
217 
218 		this(string msg, size_t line = __LINE__, string file = __FILE__) {
219 			super(msg, file, line, null);
220 		}
221 
222 		this(string lib, string symbol, size_t line = __LINE__, string file = __FILE__)
223 		{
224 			_lib = lib;
225 			_symbol = symbol;
226 			this("Failed to load symbol " ~ symbol ~ " from shared library " ~ lib, line, file);
227 		}
228 
229 		@property string lib()
230 		{
231 			return _lib;
232 		}
233 
234 		@property string symbol()
235 		{
236 			return _symbol;
237 		}
238 
239 	private:
240 		string _lib;
241 		string _symbol;
242 	}
243 
244 	pure struct Dylib
245 	{
246 		void load(string[] names)
247 		{
248 			if(isLoaded)
249 				return;
250 
251 			string[] fnames;
252 			string[] freasons;
253 
254 			foreach(name; names)
255 			{
256 				import std.stdio;
257 				import std.conv;
258 
259 				version(Posix) _handle = dlopen(name.toStringz(), RTLD_NOW);
260 				else version(Windows) _handle = LoadLibraryA(name.toStringz());
261 				if(isLoaded) {
262 					_name = name;
263 					break;
264 				}
265 
266 				fnames ~= name;
267 
268 				import std.conv : to;
269 
270 				version(Posix) {
271 					string err = to!string(dlerror());
272 					if(err is null)
273 						err = "Unknown error";
274 				}
275 				else version(Windows)
276 				{
277 					import std.windows.syserror;
278 					string err = sysErrorString(GetLastError());
279 				}
280 
281 				freasons ~= err;
282 			}
283 			if(!isLoaded)
284 				throw new DylibLoadException(fnames, freasons);
285 		}
286 
287 		void* loadSymbol(string name, bool required = true)
288 		{
289 			version(Posix) void* sym = dlsym(_handle, name.toStringz());
290 			else version(Windows) void* sym = GetProcAddress(_handle, name.toStringz());
291 
292 			if(required && !sym)
293 			{
294 				if(_callback !is null)
295 					required = _callback(name);
296 				if(required)
297 					throw new DylibSymbolLoadException(_name, name);
298 			}
299 
300 			return sym;
301 		}
302 
303 		void unload()
304 		{
305 			if(isLoaded)
306 			{
307 				version(Posix) dlclose(_handle);
308 				else version(Windows) FreeLibrary(_handle);
309 				_handle = null;
310 			}
311 		}
312 
313 		@property bool isLoaded()
314 		{
315 			return _handle !is null;
316 		}
317 
318 		@property bool delegate(string) missingSymbolCallback()
319 		{
320 			return _callback;
321 		}
322 
323 		@property void missingSymbolCallback(bool delegate(string) callback)
324 		{
325 			_callback = callback;
326 		}
327 
328 		@property void missingSymbolCallback(bool function(string) callback)
329 		{
330 			import std.functional : toDelegate;
331 			_callback = toDelegate(callback);
332 		}
333 
334 	private:
335 		string _name;
336 		void* _handle;
337 		bool delegate(string) _callback;
338 	}
339 
340 	abstract class DylibLoader
341 	{
342 		this(string libs)
343 		{
344 			string[] libs_ = libs.split(",");
345 			foreach(ref string l; libs_)
346 				l = l.strip();
347 			this(libs_);
348 		}
349 
350 		this(string[] libs)
351 		{
352 			_libs = libs;
353 			dylib.load(_libs);
354 			loadSymbols();
355 		}
356 
357 		final this(string[] libs, DylibVersion ver)
358 		{
359 			configureMinimumVersion(ver);
360 			this(libs);
361 		}
362 
363 		final this(string libs, DylibVersion ver)
364 		{
365 			configureMinimumVersion(ver);
366 			this(libs);
367 		}
368 
369 		protected void loadSymbols() {}
370 
371 		protected void configureMinimumVersion(DylibVersion minVersion)
372 		{
373 			assert(0, "DylibVersion is not supported by this loader.");
374 		}
375 
376 		protected final void bindFunc(void** ptr, string name, bool required = true)
377 		{
378 			void* func = dylib.loadSymbol(name, required);
379 			*ptr = func;
380 		}
381 
382 		protected final void bindFunc(TFUN)(ref TFUN fun, string name, bool required = true)
383 			if(isFunctionPointer!(TFUN))
384 		{
385 			void* func = dylib.loadSymbol(name, required);
386 			fun = cast(TFUN)func;
387 		}
388 
389 		protected final void bindFunc_stdcall(Func)(ref Func f, string unmangledName)
390 		{
391 			version(Win32) {
392 				import std.format : format;
393 				import std.traits : ParameterTypeTuple;
394 
395 				// get type-tuple of parameters
396 				ParameterTypeTuple!f params;
397 
398 				size_t sizeOfParametersOnStack(A...)(A args)
399 				{
400 					size_t sum = 0;
401 					foreach (arg; args) {
402 						sum += arg.sizeof;
403 
404 						// align on 32-bit stack
405 						if (sum % 4 != 0)
406 							sum += 4 - (sum % 4);
407 					}
408 					return sum;
409 				}
410 				unmangledName = format("_%s@%s", unmangledName, sizeOfParametersOnStack(params));
411 			}
412 			bindFunc(cast(void**)&f, unmangledName);
413 		}
414 
415 		@property final string[] libs()
416 		{
417 			return _libs;
418 		}
419 
420 		Dylib dylib;
421 		private string[] _libs;
422 	}
423 
424 	pragma(inline, true) void dylib_unload(void* handle)
425 	{
426 		(cast(DylibLoader)handle).dylib.unload();
427 	}
428 }
429 
430 pragma(inline, true) bool dylib_is_loaded(void* handle)
431 {
432 	version(D_BetterC) return !(handle is null);
433 	else return (cast(DylibLoader)handle).dylib.isLoaded();
434 }