⛏️ index : haiku.git

/*
 * Copyright 2002-2008, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Copyright 2001, Thomas Kurschel. All rights reserved.
 * Distributed under the terms of the NewOS License.
 */

/** Manages kernel add-ons and their exported modules. */

#include "module.h"

#include <stdlib.h>

#include "fssh_errors.h"
#include "fssh_kernel_export.h"
#include "fssh_lock.h"
#include "fssh_module.h"
#include "fssh_string.h"
#include "hash.h"


//#define TRACE_MODULE
#ifdef TRACE_MODULE
#	define TRACE(x) fssh_dprintf x
#else
#	define TRACE(x) ;
#endif
#define FATAL(x) fssh_dprintf x


namespace FSShell {


#define MODULE_HASH_SIZE 16

enum module_state {
	MODULE_QUERIED = 0,
	MODULE_LOADED,
	MODULE_INIT,
	MODULE_READY,
	MODULE_UNINIT,
	MODULE_ERROR
};


/* Each known module will have this structure which is put in the
 * gModulesHash, and looked up by name.
 */

struct module {
	struct module		*next;
	char				*name;
	char				*file;
	int32_t				ref_count;
	fssh_module_info	*info;		/* will only be valid if ref_count > 0 */
	int32_t				offset;		/* this is the offset in the headers */
	module_state		state;		/* state of module */
	uint32_t			flags;
};

#define FSSH_B_BUILT_IN_MODULE	2


/* locking scheme: there is a global lock only; having several locks
 * makes trouble if dependent modules get loaded concurrently ->
 * they have to wait for each other, i.e. we need one lock per module;
 * also we must detect circular references during init and not dead-lock
 */
static fssh_recursive_lock sModulesLock;		

/* we store the loaded modules by directory path, and all known modules by module name
 * in a hash table for quick access
 */
static hash_table *sModulesHash;


/** calculates hash for a module using its name */

static uint32_t
module_hash(void *_module, const void *_key, uint32_t range)
{
	module *module = (struct module *)_module;
	const char *name = (const char *)_key;

	if (module != NULL)
		return hash_hash_string(module->name) % range;
	
	if (name != NULL)
		return hash_hash_string(name) % range;

	return 0;
}


/** compares a module to a given name */

static int
module_compare(void *_module, const void *_key)
{
	module *module = (struct module *)_module;
	const char *name = (const char *)_key;
	if (name == NULL)
		return -1;

	return fssh_strcmp(module->name, name);
}


static inline void
inc_module_ref_count(struct module *module)
{
	module->ref_count++;
}


static inline void
dec_module_ref_count(struct module *module)
{
	module->ref_count--;
}


/** Extract the information from the module_info structure pointed at
 *	by "info" and create the entries required for access to it's details.
 */

static fssh_status_t
create_module(fssh_module_info *info, const char *file, int offset, module **_module)
{
	module *module;

	TRACE(("create_module(info = %p, file = \"%s\", offset = %d, _module = %p)\n",
		info, file, offset, _module));

	if (!info->name)
		return FSSH_B_BAD_VALUE;

	module = (struct module *)hash_lookup(sModulesHash, info->name);
	if (module) {
		FATAL(("Duplicate module name (%s) detected... ignoring new one\n", info->name));
		return FSSH_B_FILE_EXISTS;
	}

	if ((module = (struct module *)malloc(sizeof(struct module))) == NULL)
		return FSSH_B_NO_MEMORY;

	TRACE(("create_module: name = \"%s\", file = \"%s\"\n", info->name, file));

	module->name = fssh_strdup(info->name);
	if (module->name == NULL) {
		free(module);
		return FSSH_B_NO_MEMORY;
	}

	module->file = fssh_strdup(file);
	if (module->file == NULL) {
		free(module->name);
		free(module);
		return FSSH_B_NO_MEMORY;
	}

	module->state = MODULE_QUERIED;
	module->info = info;
	module->offset = offset;
		// record where the module_info can be found in the module_info array
	module->ref_count = 0;
	module->flags = info->flags;

	fssh_recursive_lock_lock(&sModulesLock);
	hash_insert(sModulesHash, module);
	fssh_recursive_lock_unlock(&sModulesLock);

	if (_module)
		*_module = module;

	return FSSH_B_OK;
}


/** Initializes a loaded module depending on its state */

static inline fssh_status_t
init_module(module *module)
{
	switch (module->state) {
		case MODULE_QUERIED:
		case MODULE_LOADED:
		{
			fssh_status_t status;
			module->state = MODULE_INIT;

			// init module

			TRACE(("initializing module %s (at %p)... \n", module->name, module->info->std_ops));
			status = module->info->std_ops(FSSH_B_MODULE_INIT);
			TRACE(("...done (%s)\n", strerror(status)));

			if (status >= FSSH_B_OK)
				module->state = MODULE_READY;
			else {
				module->state = MODULE_LOADED;
			}

			return status;
		}

		case MODULE_READY:
			return FSSH_B_OK;

		case MODULE_INIT:
			FATAL(("circular reference to %s\n", module->name));
			return FSSH_B_ERROR;

		case MODULE_UNINIT:
			FATAL(("tried to load module %s which is currently unloading\n", module->name));
			return FSSH_B_ERROR;

		case MODULE_ERROR:
			FATAL(("cannot load module %s because its earlier unloading failed\n", module->name));
			return FSSH_B_ERROR;

		default:
			return FSSH_B_ERROR;
	}
	// never trespasses here
}


/** Uninitializes a module depeding on its state */

static inline int
uninit_module(module *module)
{
	TRACE(("uninit_module(%s)\n", module->name));

	switch (module->state) {
		case MODULE_QUERIED:
		case MODULE_LOADED:
			return FSSH_B_NO_ERROR;

		case MODULE_INIT:
			fssh_panic("Trying to unload module %s which is initializing\n", module->name);
			return FSSH_B_ERROR;

		case MODULE_UNINIT:
			fssh_panic("Trying to unload module %s which is un-initializing\n", module->name);
			return FSSH_B_ERROR;

		case MODULE_READY:
		{
			fssh_status_t status;

			module->state = MODULE_UNINIT;

			TRACE(("uninitializing module %s...\n", module->name));
			status = module->info->std_ops(FSSH_B_MODULE_UNINIT);
			TRACE(("...done (%s)\n", strerror(status)));

			if (status == FSSH_B_NO_ERROR) {
				module->state = MODULE_LOADED;
				return 0;
			}

			FATAL(("Error unloading module %s (%s)\n", module->name, fssh_strerror(status)));

			module->state = MODULE_ERROR;
			module->flags |= FSSH_B_KEEP_LOADED;

			return status;
		}
		default:	
			return FSSH_B_ERROR;		
	}
	// never trespasses here
}


void
register_builtin_module(struct fssh_module_info *info)
{
	info->flags |= FSSH_B_BUILT_IN_MODULE;
		// this is an internal flag, it doesn't have to be set by modules itself

	if (create_module(info, "", -1, NULL) != FSSH_B_OK)
		fssh_dprintf("creation of built-in module \"%s\" failed!\n", info->name);
}


static int
dump_modules(int argc, char **argv)
{
	hash_iterator iterator;
	struct module *module;

	hash_rewind(sModulesHash, &iterator);
	fssh_dprintf("-- known modules:\n");

	while ((module = (struct module *)hash_next(sModulesHash, &iterator)) != NULL) {
		fssh_dprintf("%p: \"%s\", \"%s\" (%d), refcount = %d, state = %d\n",
			module, module->name, module->file, (int)module->offset, (int)module->ref_count,
			module->state);
	}

	return 0;
}


//	#pragma mark -
//	Exported Kernel API (private part)


/** Setup the module structures and data for use - must be called
 *	before any other module call.
 */

fssh_status_t
module_init(kernel_args *args)
{
	fssh_recursive_lock_init(&sModulesLock, "modules rlock");

	sModulesHash = hash_init(MODULE_HASH_SIZE, 0, module_compare, module_hash);
	if (sModulesHash == NULL)
		return FSSH_B_NO_MEMORY;

	fssh_add_debugger_command("modules", &dump_modules, "list all known & loaded modules");

	return FSSH_B_OK;
}


}	// namespace FSShell


//	#pragma mark -
//	Exported Kernel API (public part)


using namespace FSShell;


fssh_status_t
fssh_get_module(const char *path, fssh_module_info **_info)
{
	module *module;
	fssh_status_t status;

	TRACE(("get_module(%s)\n", path));

	if (path == NULL)
		return FSSH_B_BAD_VALUE;

	fssh_recursive_lock_lock(&sModulesLock);

	module = (struct module *)hash_lookup(sModulesHash, path);
	if (module == NULL)
		goto err;

	// The state will be adjusted by the call to init_module
	// if we have just loaded the file
	if (module->ref_count == 0)
		status = init_module(module);
	else
		status = FSSH_B_OK;

	if (status == FSSH_B_OK) {
		inc_module_ref_count(module);
		*_info = module->info;
	}

	fssh_recursive_lock_unlock(&sModulesLock);
	return status;

err:
	fssh_recursive_lock_unlock(&sModulesLock);
	return FSSH_B_ENTRY_NOT_FOUND;
}


fssh_status_t
fssh_put_module(const char *path)
{
	module *module;

	TRACE(("put_module(path = %s)\n", path));

	fssh_recursive_lock_lock(&sModulesLock);

	module = (struct module *)hash_lookup(sModulesHash, path);
	if (module == NULL) {
		FATAL(("module: We don't seem to have a reference to module %s\n", path));
		fssh_recursive_lock_unlock(&sModulesLock);
		return FSSH_B_BAD_VALUE;
	}
	
	if ((module->flags & FSSH_B_KEEP_LOADED) == 0) {
		dec_module_ref_count(module);

		if (module->ref_count == 0)
			uninit_module(module);
	}

	fssh_recursive_lock_unlock(&sModulesLock);
	return FSSH_B_OK;
}