1/*
2 * Copyright 2002-2008, Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Copyright 2001, Thomas Kurschel. All rights reserved.
6 * Distributed under the terms of the NewOS License.
7 */
8
9/** Manages kernel add-ons and their exported modules. */
10
11#include "module.h"
12
13#include <stdlib.h>
14
15#include "fssh_errors.h"
16#include "fssh_kernel_export.h"
17#include "fssh_lock.h"
18#include "fssh_module.h"
19#include "fssh_string.h"
20#include "hash.h"
21
22
23//#define TRACE_MODULE
24#ifdef TRACE_MODULE
25#	define TRACE(x) fssh_dprintf x
26#else
27#	define TRACE(x) ;
28#endif
29#define FATAL(x) fssh_dprintf x
30
31
32namespace FSShell {
33
34
35#define MODULE_HASH_SIZE 16
36
37enum module_state {
38	MODULE_QUERIED = 0,
39	MODULE_LOADED,
40	MODULE_INIT,
41	MODULE_READY,
42	MODULE_UNINIT,
43	MODULE_ERROR
44};
45
46
47/* Each known module will have this structure which is put in the
48 * gModulesHash, and looked up by name.
49 */
50
51struct module {
52	struct module		*next;
53	char				*name;
54	char				*file;
55	int32_t				ref_count;
56	fssh_module_info	*info;		/* will only be valid if ref_count > 0 */
57	int32_t				offset;		/* this is the offset in the headers */
58	module_state		state;		/* state of module */
59	uint32_t			flags;
60};
61
62#define FSSH_B_BUILT_IN_MODULE	2
63
64
65/* locking scheme: there is a global lock only; having several locks
66 * makes trouble if dependent modules get loaded concurrently ->
67 * they have to wait for each other, i.e. we need one lock per module;
68 * also we must detect circular references during init and not dead-lock
69 */
70static fssh_recursive_lock sModulesLock;
71
72/* we store the loaded modules by directory path, and all known modules by module name
73 * in a hash table for quick access
74 */
75static hash_table *sModulesHash;
76
77
78/** calculates hash for a module using its name */
79
80static uint32_t
81module_hash(void *_module, const void *_key, uint32_t range)
82{
83	module *module = (struct module *)_module;
84	const char *name = (const char *)_key;
85
86	if (module != NULL)
87		return hash_hash_string(module->name) % range;
88
89	if (name != NULL)
90		return hash_hash_string(name) % range;
91
92	return 0;
93}
94
95
96/** compares a module to a given name */
97
98static int
99module_compare(void *_module, const void *_key)
100{
101	module *module = (struct module *)_module;
102	const char *name = (const char *)_key;
103	if (name == NULL)
104		return -1;
105
106	return fssh_strcmp(module->name, name);
107}
108
109
110static inline void
111inc_module_ref_count(struct module *module)
112{
113	module->ref_count++;
114}
115
116
117static inline void
118dec_module_ref_count(struct module *module)
119{
120	module->ref_count--;
121}
122
123
124/** Extract the information from the module_info structure pointed at
125 *	by "info" and create the entries required for access to it's details.
126 */
127
128static fssh_status_t
129create_module(fssh_module_info *info, const char *file, int offset, module **_module)
130{
131	module *module;
132
133	TRACE(("create_module(info = %p, file = \"%s\", offset = %d, _module = %p)\n",
134		info, file, offset, _module));
135
136	if (!info->name)
137		return FSSH_B_BAD_VALUE;
138
139	module = (struct module *)hash_lookup(sModulesHash, info->name);
140	if (module) {
141		FATAL(("Duplicate module name (%s) detected... ignoring new one\n", info->name));
142		return FSSH_B_FILE_EXISTS;
143	}
144
145	if ((module = (struct module *)malloc(sizeof(struct module))) == NULL)
146		return FSSH_B_NO_MEMORY;
147
148	TRACE(("create_module: name = \"%s\", file = \"%s\"\n", info->name, file));
149
150	module->name = fssh_strdup(info->name);
151	if (module->name == NULL) {
152		free(module);
153		return FSSH_B_NO_MEMORY;
154	}
155
156	module->file = fssh_strdup(file);
157	if (module->file == NULL) {
158		free(module->name);
159		free(module);
160		return FSSH_B_NO_MEMORY;
161	}
162
163	module->state = MODULE_QUERIED;
164	module->info = info;
165	module->offset = offset;
166		// record where the module_info can be found in the module_info array
167	module->ref_count = 0;
168	module->flags = info->flags;
169
170	fssh_recursive_lock_lock(&sModulesLock);
171	hash_insert(sModulesHash, module);
172	fssh_recursive_lock_unlock(&sModulesLock);
173
174	if (_module)
175		*_module = module;
176
177	return FSSH_B_OK;
178}
179
180
181/** Initializes a loaded module depending on its state */
182
183static inline fssh_status_t
184init_module(module *module)
185{
186	switch (module->state) {
187		case MODULE_QUERIED:
188		case MODULE_LOADED:
189		{
190			fssh_status_t status;
191			module->state = MODULE_INIT;
192
193			// init module
194
195			TRACE(("initializing module %s (at %p)... \n", module->name, module->info->std_ops));
196			status = module->info->std_ops(FSSH_B_MODULE_INIT);
197			TRACE(("...done (%s)\n", strerror(status)));
198
199			if (status >= FSSH_B_OK)
200				module->state = MODULE_READY;
201			else {
202				module->state = MODULE_LOADED;
203			}
204
205			return status;
206		}
207
208		case MODULE_READY:
209			return FSSH_B_OK;
210
211		case MODULE_INIT:
212			FATAL(("circular reference to %s\n", module->name));
213			return FSSH_B_ERROR;
214
215		case MODULE_UNINIT:
216			FATAL(("tried to load module %s which is currently unloading\n", module->name));
217			return FSSH_B_ERROR;
218
219		case MODULE_ERROR:
220			FATAL(("cannot load module %s because its earlier unloading failed\n", module->name));
221			return FSSH_B_ERROR;
222
223		default:
224			return FSSH_B_ERROR;
225	}
226	// never trespasses here
227}
228
229
230/** Uninitializes a module depeding on its state */
231
232static inline int
233uninit_module(module *module)
234{
235	TRACE(("uninit_module(%s)\n", module->name));
236
237	switch (module->state) {
238		case MODULE_QUERIED:
239		case MODULE_LOADED:
240			return FSSH_B_NO_ERROR;
241
242		case MODULE_INIT:
243			fssh_panic("Trying to unload module %s which is initializing\n", module->name);
244			return FSSH_B_ERROR;
245
246		case MODULE_UNINIT:
247			fssh_panic("Trying to unload module %s which is un-initializing\n", module->name);
248			return FSSH_B_ERROR;
249
250		case MODULE_READY:
251		{
252			fssh_status_t status;
253
254			module->state = MODULE_UNINIT;
255
256			TRACE(("uninitializing module %s...\n", module->name));
257			status = module->info->std_ops(FSSH_B_MODULE_UNINIT);
258			TRACE(("...done (%s)\n", strerror(status)));
259
260			if (status == FSSH_B_NO_ERROR) {
261				module->state = MODULE_LOADED;
262				return 0;
263			}
264
265			FATAL(("Error unloading module %s (%s)\n", module->name, fssh_strerror(status)));
266
267			module->state = MODULE_ERROR;
268			module->flags |= FSSH_B_KEEP_LOADED;
269
270			return status;
271		}
272		default:
273			return FSSH_B_ERROR;
274	}
275	// never trespasses here
276}
277
278
279void
280register_builtin_module(struct fssh_module_info *info)
281{
282	info->flags |= FSSH_B_BUILT_IN_MODULE;
283		// this is an internal flag, it doesn't have to be set by modules itself
284
285	if (create_module(info, "", -1, NULL) != FSSH_B_OK)
286		fssh_dprintf("creation of built-in module \"%s\" failed!\n", info->name);
287}
288
289
290static int
291dump_modules(int argc, char **argv)
292{
293	hash_iterator iterator;
294	struct module *module;
295
296	hash_rewind(sModulesHash, &iterator);
297	fssh_dprintf("-- known modules:\n");
298
299	while ((module = (struct module *)hash_next(sModulesHash, &iterator)) != NULL) {
300		fssh_dprintf("%p: \"%s\", \"%s\" (%d), refcount = %d, state = %d\n",
301			module, module->name, module->file, (int)module->offset, (int)module->ref_count,
302			module->state);
303	}
304
305	return 0;
306}
307
308
309//	#pragma mark -
310//	Exported Kernel API (private part)
311
312
313/** Setup the module structures and data for use - must be called
314 *	before any other module call.
315 */
316
317fssh_status_t
318module_init(kernel_args *args)
319{
320	fssh_recursive_lock_init(&sModulesLock, "modules rlock");
321
322	sModulesHash = hash_init(MODULE_HASH_SIZE, 0, module_compare, module_hash);
323	if (sModulesHash == NULL)
324		return FSSH_B_NO_MEMORY;
325
326	fssh_add_debugger_command("modules", &dump_modules, "list all known & loaded modules");
327
328	return FSSH_B_OK;
329}
330
331
332}	// namespace FSShell
333
334
335//	#pragma mark -
336//	Exported Kernel API (public part)
337
338
339using namespace FSShell;
340
341
342fssh_status_t
343fssh_get_module(const char *path, fssh_module_info **_info)
344{
345	module *module;
346	fssh_status_t status;
347
348	TRACE(("get_module(%s)\n", path));
349
350	if (path == NULL)
351		return FSSH_B_BAD_VALUE;
352
353	fssh_recursive_lock_lock(&sModulesLock);
354
355	module = (struct module *)hash_lookup(sModulesHash, path);
356	if (module == NULL)
357		goto err;
358
359	// The state will be adjusted by the call to init_module
360	// if we have just loaded the file
361	if (module->ref_count == 0)
362		status = init_module(module);
363	else
364		status = FSSH_B_OK;
365
366	if (status == FSSH_B_OK) {
367		inc_module_ref_count(module);
368		*_info = module->info;
369	}
370
371	fssh_recursive_lock_unlock(&sModulesLock);
372	return status;
373
374err:
375	fssh_recursive_lock_unlock(&sModulesLock);
376	return FSSH_B_ENTRY_NOT_FOUND;
377}
378
379
380fssh_status_t
381fssh_put_module(const char *path)
382{
383	module *module;
384
385	TRACE(("put_module(path = %s)\n", path));
386
387	fssh_recursive_lock_lock(&sModulesLock);
388
389	module = (struct module *)hash_lookup(sModulesHash, path);
390	if (module == NULL) {
391		FATAL(("module: We don't seem to have a reference to module %s\n", path));
392		fssh_recursive_lock_unlock(&sModulesLock);
393		return FSSH_B_BAD_VALUE;
394	}
395
396	if ((module->flags & FSSH_B_KEEP_LOADED) == 0) {
397		dec_module_ref_count(module);
398
399		if (module->ref_count == 0)
400			uninit_module(module);
401	}
402
403	fssh_recursive_lock_unlock(&sModulesLock);
404	return FSSH_B_OK;
405}
406