diff --git a/src/tests/servers/net/Jamfile b/src/tests/servers/net/Jamfile new file mode 100644 index 0000000000..ebb0065be2 --- /dev/null +++ b/src/tests/servers/net/Jamfile @@ -0,0 +1,3 @@ +SubDir OBOS_TOP src tests servers net ; + +SubInclude OBOS_TOP src tests servers net userland_modules ; diff --git a/src/tests/servers/net/userland_modules/Jamfile b/src/tests/servers/net/userland_modules/Jamfile new file mode 100644 index 0000000000..a3bf13804d --- /dev/null +++ b/src/tests/servers/net/userland_modules/Jamfile @@ -0,0 +1,6 @@ +SubDir OBOS_TOP src tests servers net userland_modules ; + +SimpleTest userland_modules + : userland_modules.cpp + : be root +; diff --git a/src/tests/servers/net/userland_modules/README b/src/tests/servers/net/userland_modules/README new file mode 100644 index 0000000000..54c1532e3e --- /dev/null +++ b/src/tests/servers/net/userland_modules/README @@ -0,0 +1,24 @@ +KERNEL MODULES USERLAND EMULATION TESTBED + +A quick testbed to code, debug and stress the userland emulation of kernel modules support we need +for the net_server-like debugging environment to support addons as they were modules, exactly like in kernel space. + +The network stack modules can be compiled as kernel modules *or* userland addons. +The userland versions of these modules will be searched there, in this order: + +./add-ons +/boot/home/config/add-ons/net_server +/boot/beos/system/add-ons/net_server + +As they rely on kernel modules API to works, we have to implement an userland version of +this API : + +// loading / unloading modules: +status_t get_module(const char *module_name, module_info **mi); +status_t put_module(const char *module_info); + +// querying modules: +void * open_module_list(const char *prefix); +status_t read_next_module_name(void *cookie, char *buf, size_t *bufsize); +status_t close_module_list(void *cookie); +status_t get_next_loaded_module_name(uint32 *cookie, char *buf, size_t *bufsize); diff --git a/src/tests/servers/net/userland_modules/userland_modules.cpp b/src/tests/servers/net/userland_modules/userland_modules.cpp new file mode 100644 index 0000000000..95054ce383 --- /dev/null +++ b/src/tests/servers/net/userland_modules/userland_modules.cpp @@ -0,0 +1,653 @@ +/* Userland modules emulation support +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASSERT(condition) if (!(condition)) { debugger("Assertion failed!"); } + +typedef enum { + MODULE_LOADED = 0, + MODULE_INITING, + MODULE_READY, + MODULE_UNINITING, + MODULE_ERROR +} module_state; + +typedef struct module { + struct module * next; + uint32 id; + char * name; + module_info * info; + struct module_addon * addon; // the module addon this module live in + // if NULL, builtin module addon + int32 ref_count; // reference count of get_module() made on this module + bool keep_loaded; + module_state state; +} module; + +typedef struct module_addon { + struct module_addon * next; + int32 ref_count; // reference count of get_module() made using this addon + bool keep_loaded; + char * path; + image_id addon_image; // if -1, not loaded in memory currently + module_info ** infos; // valid only when addon_image != -1 +} module_addon; + +typedef struct module_list_cookie { + char * prefix; + // TODO +} module_list_cookie; + +#define LOCK_MODULES acquire_sem(g_modules_lock) +#define UNLOCK_MODULES release_sem(g_modules_lock) + +// local prototypes +// ------------------ + +static module * search_module(const char * name); +static status_t init_module(module * m); +static status_t uninit_module(module * m); +static module * find_loaded_module_by_name(const char * name); +static module * find_loaded_module_by_id(uint32 id); + +static module_addon * load_module_addon(const char * path); +static status_t unload_module_addon(module_addon * ma); + +// globals +// ------------------ + +static sem_id g_modules_lock = -1; // One lock for rule them all, etc... +static module * g_modules = NULL; +static module_addon * g_module_addons = NULL; +static int32 g_next_module_id = 1; + + +// Public routines +// --------------- + +status_t get_module(const char * name, module_info ** mi) +{ + module * m; + + printf("get_module(%s)\n", name); + + m = find_loaded_module_by_name(name); + if (!m) + m = search_module(name); + + if (!m) + return B_NAME_NOT_FOUND; + + *mi = m->info; + + if (m->addon) // built-in modules don't comes from addon... + atomic_add(&m->addon->ref_count, 1); + + if (atomic_add(&m->ref_count, 1) == 0) + // first time we reference this module, so let's init it: + return init_module(m); + + return B_OK; +} + +status_t put_module(const char * name) +{ + module * m; + + printf("put_module(%s)\n", name); + + m = find_loaded_module_by_name(name); + if (!m) + // Hum??? Sorry, this module name was never get_module()'d + return B_NAME_NOT_FOUND; + + if (atomic_add(&m->ref_count, -1) <= 1) + // this module is no more used... + uninit_module(m); + + if (!m->addon) + // built-in modules are module addon less... + return B_OK; + + if (atomic_add(&m->addon->ref_count, -1) > 1) + // Still other module(s) using this module addon + return B_OK; + + // okay, this module addon is no more used + // let's free up some memory + return unload_module_addon(m->addon); +} + + +status_t get_next_loaded_module_name(uint32 *cookie, char *buf, size_t *bufsize) +{ + module * m; + status_t status; + + if (buf == NULL && bufsize == NULL) + return B_BAD_VALUE; + + LOCK_MODULES; + + if (*cookie == 0) + // first call expected value + m = g_modules; + else { + // find last loaded module returned, and seek to next one + m = (module *) find_loaded_module_by_id((int) *cookie); + if (m) + m = m->next; + }; + + // find next loaded module + while (m) { + if (m->ref_count) + break; + m = m->next; + }; + + status = B_OK; + if (m) { + ASSERT(m->info); + if (buf != NULL) + strncpy(buf, m->info->name, *bufsize); + else + *bufsize = strlen(m->info->name + 1); + *cookie = m->id; + } else + status = B_BAD_INDEX; + + UNLOCK_MODULES; + + return status; +} + + +void * open_module_list(const char *prefix) +{ + module_list_cookie * mlc; + + if (prefix == NULL) + return NULL; + + mlc = (module_list_cookie *) malloc(sizeof(*mlc)); + mlc->prefix = strdup(prefix); + + return mlc; +} + + +status_t read_next_module_name(void *cookie, char *buf, size_t *bufsize) +{ + module_list_cookie * mlc = (module_list_cookie *) cookie; + + if (!mlc) + return B_BAD_VALUE; + + // TODO + return B_ERROR; +} + + +status_t close_module_list(void *cookie) +{ + module_list_cookie * mlc = (module_list_cookie *) cookie; + + ASSERT(mlc); + ASSERT(mlc->prefix); + + free(mlc->prefix); + free(mlc); + + return B_ERROR; +} + + +// #pragma mark - +// Private routines + +static module_addon * load_module_addon(const char * path) +{ + module_addon * ma; + image_id addon_id; + module_info ** mi; + status_t status; + + ASSERT(path); + + printf("Loading %s addon\n", path); + addon_id = load_add_on(path); + if (addon_id < 0) + return NULL; + + ma = NULL; + + status = get_image_symbol(addon_id, "modules", B_SYMBOL_TYPE_DATA, (void **) &mi); + if (status != B_OK) { + // No "modules" symbol found in this addon + printf("No \"modules\" symbol found in %s addon: not a module addon!\n", path); + goto error; + }; + + ma = (module_addon *) malloc(sizeof(*ma)); + if (!ma) + // Gasp: not enough memory! + goto error; + + LOCK_MODULES; + + ma->ref_count = 0; + ma->keep_loaded = false; + ma->path = strdup(path); + ma->addon_image = addon_id; + ma->infos = mi; + + while(*mi) { + module * m; + + m = (module *) malloc(sizeof(*m)); + if (!m) + // Gasp, again: not enough memory! + goto error; + + m->ref_count = 0; + m->id = atomic_add(&g_next_module_id, 1); + m->info = (*mi); + m->name = strdup(m->info->name); + m->addon = ma; + m->keep_loaded = (m->info->flags & B_KEEP_LOADED) ? true : false; + + m->state = MODULE_LOADED; + + m->next = g_modules; + g_modules = m; + + mi++; + }; + + // add this module addon to the list + ma->next = g_module_addons; + g_module_addons = ma; + + UNLOCK_MODULES; + + return ma; + +error: + printf("Error while load_module_addon(%s)\n", path); + + if (ma) { + // remove any appended modules by this module addon until we got error... + module * prev; + module * m; + + prev = NULL; + m = g_modules; + while (m) { + if (m->addon == ma) { + module * tmp = m; + + m = tmp->next; + + if (prev) + prev->next = tmp->next; + else + g_modules = tmp->next; + + if (tmp->name) + free(tmp->name); + free(tmp); + continue; + }; + + prev = m; + m = m->next; + }; + + + UNLOCK_MODULES; + + if (ma->path) + free(ma->path); + free(ma); + }; + + printf("Unloading %s addon\n", path); + unload_add_on(addon_id); + return NULL; +} + +static status_t unload_module_addon(module_addon * ma) +{ + module * m; + module * prev; + status_t status; + + ASSERT(ma); + + if (ma->keep_loaded) { + printf("B_KEEP_LOADED flag set for %s module addon. Will be *never* unloaded!\n", + ma->path); + return B_OK; + }; + + if (ma->ref_count) + // still someone needing this module addon, it seems? + return B_OK; + + if (ma->addon_image < 0) + return B_OK; + + status = B_ERROR; + + printf("Unloading %s addon\n", ma->path); + status = unload_add_on(ma->addon_image); + if (status != B_OK) { + printf("unload_add_on(%ld -> %s) failed: %ld!\n", ma->addon_image, ma->path, status); + return status; + }; + + LOCK_MODULES; + + // remove the modules coming from this module addon from g_modules list + prev = NULL; + m = g_modules; + while (m) { + if (m->addon == ma) { + module * tmp = m; + + m = tmp->next; + + if (prev) + prev->next = tmp->next; + else + g_modules = tmp->next; + + if (tmp->name) + free(tmp->name); + free(tmp); + continue; + }; + + prev = m; + m = m->next; + }; + + // remove the module addon from g_module_addons list: + if (g_module_addons == ma) + g_module_addons = ma->next; + else { + module_addon * tmp; + tmp = g_module_addons; + while (tmp && tmp->next != ma) + tmp = tmp->next; + + ASSERT(tmp); + tmp->next = ma->next; + }; + + if (ma->path) + free(ma->path); + free(ma); + + UNLOCK_MODULES; + + return B_OK; +} + + +static module * search_module(const char * name) +{ + BPath path; + BPath addons_path; + BEntry entry; + int i; + module * found_module; + directory_which addons_paths[] = { + (directory_which) -2, // APP_ADDONS_DIRECTORY + B_USER_ADDONS_DIRECTORY, + // B_COMMON_ADDONS_DIRECTORY, + B_BEOS_ADDONS_DIRECTORY, + (directory_which) -1 + }; + + printf("search_module(%s):\n", name); + + found_module = NULL; + for (i = 0; addons_paths[i] != -1 && found_module == NULL; i++) { + if (addons_paths[i] == -2) { + // compute "%A/add-ons" path + app_info ai; + be_app->GetAppInfo(&ai); + entry.SetTo(&ai.ref); + entry.GetPath(&addons_path); + addons_path.GetParent(&addons_path); + addons_path.Append("add-ons"); + } else { + // try in "net_server" subfolder of officials add-ons repositories + if (find_directory(addons_paths[i], &addons_path, false, NULL) != B_OK) + continue; + addons_path.Append("net_server"); + }; + + printf("Looking into %s\n", addons_path.Path()); + + path.SetTo(addons_path.Path()); + path.Append(name); + + while(path != addons_path) { + printf(" %s ?\n", path.Path()); + entry.SetTo(path.Path()); + if (entry.IsFile()) { + module_addon * ma; + + // try to load the module addon + ma = load_module_addon(path.Path()); + if (ma) { + found_module = find_loaded_module_by_name(name); + if (found_module) + break; + + unload_module_addon(ma); + }; // if (ma) + }; // if (entry.IsFile()) + + // okay, remove the current path leaf and try again... + path.GetParent(&path); + }; + }; + + if (found_module) + printf(" Found it in %s addon module!\n", + found_module->addon ? found_module->addon->path : "BUILTIN"); + + return found_module; +} + + +static status_t init_module(module * m) +{ + status_t status; + + ASSERT(m); + + switch (m->state) { + case MODULE_LOADED: + m->state = MODULE_INITING; + ASSERT(m->info); + printf("Initing module %s... ", m->name); + status = m->info->std_ops(B_MODULE_INIT); + printf("done (%s).\n", strerror(status)); + m->state = (status == B_OK) ? MODULE_READY : MODULE_LOADED; + + if (m->state == MODULE_READY && m->keep_loaded && m->addon) { + // one module (at least) was inited and request to never being + // unload from memory, so keep the corresponding addon loaded + printf("module %s set B_KEEP_LOADED flag:\nmodule addon %s will never be unloaded!\n", + m->name, m->addon->path); + m->addon->keep_loaded = true; + }; + break; + + case MODULE_READY: + status = B_OK; + break; + + case MODULE_INITING: // circular reference!!! + case MODULE_UNINITING: // initing a module currently unloading... + case MODULE_ERROR: // module failed to unload previously... + default: // Unknown module state!!! + status = B_ERROR; + break; + }; + + return status; +} + + +static status_t uninit_module(module * m) +{ + status_t status; + + ASSERT(m); + + switch (m->state) { + case MODULE_READY: + m->state = MODULE_UNINITING; + ASSERT(m->info); + printf("Uniniting module %s... ", m->name); + status = m->info->std_ops(B_MODULE_UNINIT); + printf("done (%s).\n", strerror(status)); + m->state = (status == B_OK) ? MODULE_LOADED : MODULE_ERROR; + break; + + case MODULE_LOADED: + // No need to uninit it, all is fine so. + status = B_OK; + break; + + case MODULE_INITING: // uniniting while initializing + case MODULE_UNINITING: // uniniting already pending + case MODULE_ERROR: // module failed previously... + default: // Unknown module state!!! + status = B_ERROR; + break; + }; + + return status; +} + + +static module * find_loaded_module_by_name(const char * name) +{ + module * m; + + LOCK_MODULES; + + m = g_modules; + while (m) { + if (strcmp(name, m->name) == 0) + break; + m = m->next; + }; + + UNLOCK_MODULES; + return m; +} + + +static module * find_loaded_module_by_id(uint32 id) +{ + module * m; + + LOCK_MODULES; + + m = g_modules; + while (m) { + if (m->id == id) + break; + m = m->next; + }; + + UNLOCK_MODULES; + return m; +} + + +// #pragma mark - + +#define NET_CORE_MODULE_NAME "network/core/v1" +#define NET_ETHERNET_MODULE_NAME "network/interfaces/ethernet" +#define NET_IPV4_MODULE_NAME "network/protocols/ipv4/v1" + +#define MODULE_LIST_PREFIX "network/protocols" + +int main(int argc, char **argv) +{ + module_info * core; + module_info * ethernet; + module_info * ipv4; + char module_name[256]; + uint32 cookie; + size_t sz; + void * ml_cookie; + + new BApplication("application/x-vnd-OBOS-net_server"); + + printf("open_module_list(%s):\n", MODULE_LIST_PREFIX); + ml_cookie = open_module_list(MODULE_LIST_PREFIX); + sz = sizeof(module_name); + while(read_next_module_name(ml_cookie, module_name, &sz) == B_OK) { + printf(" %s\n", module_name); + sz = sizeof(module_name); + }; + close_module_list(ml_cookie); + printf("close_module_list()\n"); + + + core = NULL; + get_module(NET_CORE_MODULE_NAME, (module_info **) &core); + + ethernet = NULL; + get_module(NET_ETHERNET_MODULE_NAME, (module_info **) ðernet); + + ipv4 = NULL; + get_module(NET_IPV4_MODULE_NAME, (module_info **) &ipv4); + + printf("get_next_loaded_module_name() test:\n"); + cookie = 0; + sz = sizeof(module_name); + while (get_next_loaded_module_name(&cookie, module_name, &sz) == B_OK) + printf("%ld: %s\n", cookie, module_name); + + if (ipv4) + put_module(NET_IPV4_MODULE_NAME); + + if (ethernet) + put_module(NET_ETHERNET_MODULE_NAME); + + if (core) + put_module(NET_CORE_MODULE_NAME); + + printf("get_next_loaded_module_name() test:\n"); + cookie = 0; + sz = sizeof(module_name); + while (get_next_loaded_module_name(&cookie, module_name, &sz) == B_OK) + printf("%ld: %s\n", cookie, module_name); + + delete be_app; + return 0; +} +