summaryrefslogtreecommitdiff
path: root/libmodman/module_manager.cpp
blob: 88e619845607c732ae26971c53cd61efe113185c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/*******************************************************************************
 * libmodman - A library for extending applications
 * Copyright (C) 2009 Nathaniel McCallum <nathaniel@natemccallum.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 ******************************************************************************/

#include <algorithm>  // For sort()
#include <sys/stat.h> // For stat()
#include <iostream>
#include <typeinfo>

#ifdef WIN32
#include <windows.h>
#else
#include <dlfcn.h>  // For dlopen(), etc...
#include <dirent.h> // For opendir(), readdir(), closedir()
#endif

#include "module_manager.hpp"
using namespace libmodman;

#include <cstdio>

#define _LOAD_FAIL -1
#define _LOAD_LAZY  0
#define _LOAD_SUCC  1

#ifdef WIN32
#define pdlmtype HMODULE
#define pdlopenl(filename) LoadLibraryEx(filename, NULL, DONT_RESOLVE_DLL_REFERENCES)
#define pdlclose(module) FreeLibrary((pdlmtype) module)
static void* pdlsym(pdlmtype mod, const string &sym) {
	return (void *) GetProcAddress(mod, sym.c_str());
}

static pdlmtype pdlreopen(const char* filename, pdlmtype module) {
	pdlclose(module);
	return LoadLibrary(filename);
}

static string pdlerror() {
	std::string e;
	LPTSTR msg;

	FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER |FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
			(LPTSTR) &msg,
			0,
			NULL);
	e = std::string((const char*) msg);
    LocalFree(msg);
    return e;
}

static bool pdlsymlinked(const char* modn, const char* symb) {
	return (GetProcAddress(GetModuleHandle(modn), symb) != NULL || \
		    GetProcAddress(GetModuleHandle(NULL), symb) != NULL);
}

static string prep_type_name(string name) {
	string prefix = "<class ";
	string suffix = ",";
	if (name.find(prefix) != name.npos)
		name = name.substr(name.find(prefix) + prefix.size());
	if (name.find(suffix) != name.npos)
		name = name.substr(0, name.find(suffix));
	return name;
}
#else
#define pdlmtype void*
#define pdlopenl(filename) dlopen(filename, RTLD_LAZY | RTLD_LOCAL)
#define pdlclose(module) dlclose((pdlmtype) module)
#define pdlreopen(filename, module) module
static void* pdlsym(pdlmtype mod, const string &sym) {
	return dlsym(mod, sym.c_str());
}

static string pdlerror() {
	return dlerror();
}

bool pdlsymlinked(const char* modn, const char* symb) {
	void* mod = dlopen(NULL, RTLD_LAZY | RTLD_LOCAL);
	if (mod) {
		void* sym = dlsym(mod, symb);
		dlclose(mod);
		return sym != NULL;
	}
	return false;
}

#define prep_type_name(name) name
#endif

#define _str(s) #s
#define __str(s) _str(s)

#ifndef _MOD_SUFFIX
#ifdef WIN32
#define _MOD_SUFFIX "dll"
#define CR ""
#else
#define _MOD_SUFFIX "so"
#define CR "\r"
#endif
#endif

module_manager::~module_manager() {
	// Free all extensions
	for (map<string, vector<base_extension*> >::iterator i=this->extensions.begin() ; i != this->extensions.end() ; i++) {
		for (vector<base_extension*>::iterator j=i->second.begin() ; j != i->second.end() ; j++)
			delete *j;
		i->second.clear();
	}
	this->extensions.clear();

	// Free all modules
	for (set<void*>::iterator i=this->modules.begin() ; i != this->modules.end() ; i++)
		pdlclose(*i);
	this->modules.clear();
}

static int load(map<string, vector<base_extension*> >& extensions,
                             set<string>&              singletons,
                             mm_module                *mod,
                             bool                      lazy,
                             bool                      symbreq) {
	const char* debug = getenv("_MM_DEBUG");

	if (!mod || mod->vers != __MM_MODULE_VERSION || !mod->type || !mod->init) {
		if (debug)
			cerr << "failed!" << endl
			     << "\tUnable to find basic module info!" << endl;
		return _LOAD_FAIL;
	}

	// Get the module type
	string types = mod->type();

	// Make sure the type is registered
	if (extensions.find(types) == extensions.end()) {
		if (debug)
			cerr << "failed!" << endl
				 << "\tUnknown extension type: " << prep_type_name(types) << endl;
		return _LOAD_FAIL;
	}

	// If this is a singleton and we already have an instance, don't instantiate
	if (singletons.find(types) != singletons.end() &&
		extensions[types].size() > 0) {
		if (debug)
			cerr << "failed!" << endl
			     << "\tNot loading subsequent singleton for: " << prep_type_name(types) << endl;
		return _LOAD_FAIL;
	}

	// If a symbol is defined, we'll search for it in the main process
	if (mod->symb && mod->smod && !pdlsymlinked(mod->smod, mod->symb)) {
		// If the symbol is not found and the symbol is required, error
		if (symbreq) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tUnable to find required symbol: "
					 << mod->symb << endl;
			return _LOAD_FAIL;
		}

		// If the symbol is not found and not required, we'll load only
		// if there are no other modules of this type
		else if (extensions[types].size() > 0) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tUnable to find required symbol: "
					 << mod->symb << endl;
			return _LOAD_FAIL;
		}
	}

	// We've passed all the tests this far, do it again in non-lazy mode
	if (lazy) return _LOAD_LAZY;

	// If our execution test succeeds, call init()
	if ((mod->test && mod->test()) || !mod->test) {
		base_extension** exts = mod->init();
		if (!exts) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tinit() returned no extensions!" << endl;
			return _LOAD_FAIL;
		}

		if (debug)
			cerr << "success" << endl;

		// init() returned extensions we need to register
		for (unsigned int i=0 ; exts[i] ; i++) {
			if (debug)
				cerr << "\tRegistering "
					 << typeid(*exts[i]).name() << "("
					 << prep_type_name(exts[i]->get_base_type()) << ")" << endl;
			extensions[exts[i]->get_base_type()].push_back(exts[i]);
		}
		delete[] exts;
		return _LOAD_SUCC;
	}

	if (debug)
		cerr << "failed!" << endl
			 << "\tTest execution failed." << endl;
	return _LOAD_FAIL;
}

bool module_manager::load_builtin(mm_module *mod) {
	const char* debug = getenv("_MM_DEBUG");
	if (debug)
			cerr << "loading : builtin module " << mod->name << CR;

	// Do the load with the specified prefix
	int status = load(this->extensions, this->singletons, mod, false, false);
	return status == _LOAD_SUCC;
}

bool module_manager::load_file(const string &filename, bool symbreq) {
	const char* debug = getenv("_MM_DEBUG");

	// Stat the file to make sure it is a file
	struct stat st;
	if (stat(filename.c_str(), &st) != 0) return false;
	if ((st.st_mode & S_IFMT) != S_IFREG) return false;

	if (debug)
		cerr << "loading : " << filename << CR;

	// Open the module
	pdlmtype dlobj = pdlopenl(filename.c_str());
	if (!dlobj) {
		if (debug)
			cerr << "failed!" << endl
			     << "\t" << pdlerror() << endl;
		return false;
	}

	// If we have already loaded this module, return true
	if (this->modules.find((void*) dlobj) != this->modules.end()) {
		if (debug)
			cerr << "preload" << endl;
		pdlclose(dlobj);
		return true;
	}

	// Try and finish the load
	struct mm_module *mod_info = (mm_module*) pdlsym(dlobj, __str(__MM_MODULE_VARNAME(info)));
	int status = load(this->extensions, this->singletons, mod_info, true, symbreq);
	if (status == _LOAD_LAZY) { // Reload the module in non-lazy mode
		dlobj = pdlreopen(filename.c_str(), dlobj);
		if (!dlobj) {
			if (debug)
				cerr << "failed!" << endl
					 << "\tUnable to reload module: " << pdlerror() << endl;
			return false;
		}
		mod_info = (mm_module*) pdlsym(dlobj, __str(__MM_MODULE_VARNAME(info)));
		status = load(this->extensions, this->singletons, mod_info, false, symbreq);
	}
	if (status == _LOAD_FAIL) {
		pdlclose(dlobj);
		return false;
	}

	// Add the dlobject to our known modules
	this->modules.insert((void*) dlobj);

	// Yay, we did it!
	return true;
}

bool module_manager::load_dir(const string &dirname, bool symbreq) {
	vector<string> files;

#ifdef WIN32
	WIN32_FIND_DATA fd;
	HANDLE search;

	string srch = dirname + "\\*." + _MOD_SUFFIX;
	search = FindFirstFile(srch.c_str(), &fd);
	if (search != INVALID_HANDLE_VALUE) {
		do {
			files.push_back(dirname + "\\" + fd.cFileName);
		} while (FindNextFile(search, &fd));
		FindClose(search);
	}
#else
	struct dirent *ent;

	DIR *moduledir = opendir(dirname.c_str());
	if (moduledir) {
		while((ent = readdir(moduledir))) {
			string tmp = ent->d_name;
			if (tmp.find(_MOD_SUFFIX, tmp.size() - string(_MOD_SUFFIX).size()) != tmp.npos)
				files.push_back(dirname + "/" + tmp);
		}
		closedir(moduledir);
	}
#endif

	// Perform our load alphabetically
	sort(files.begin(), files.end());

	// Try to do the load
	bool loaded = false;
	for (vector<string>::iterator it = files.begin() ; it != files.end() ; it++)
		loaded = this->load_file(*it, symbreq) || loaded;
	return loaded;
}