/* * $HEADER$ */ #include "lam_config.h" #include #include #include #include /* Ensure to get the right */ #include "mca/ltdl.h" #include "lam/constants.h" #include "lam/util/output.h" #include "lam/lfc/list.h" #include "mca/mca.h" #include "mca/lam/base/base.h" /* * Private types */ typedef enum module_status { UNVISITED, FAILED_TO_LOAD, CHECKING_CYCLE, LOADED, STATUS_MAX } module_status_t; struct module_file_item_t { lam_list_item_t super; char type[MCA_BASE_MAX_TYPE_NAME_LEN]; char name[MCA_BASE_MAX_MODULE_NAME_LEN]; char basename[LAM_PATH_MAX]; char filename[LAM_PATH_MAX]; module_status_t status; }; typedef struct module_file_item_t module_file_item_t; struct dependency_item_t { lam_list_item_t super; module_file_item_t *di_module_file_item; }; typedef struct dependency_item_t dependency_item_t; struct ltfn_data_holder_t { char type[MCA_BASE_MAX_TYPE_NAME_LEN]; char name[MCA_BASE_MAX_MODULE_NAME_LEN]; }; typedef struct ltfn_data_holder_t ltfn_data_holder_t; /* * Private functions */ static void find_dyn_modules(const char *path, const char *type, const char *name, lam_list_t *found_modules); static int save_filename(const char *filename, lt_ptr data); static int open_module(module_file_item_t *target_file, lam_list_t *found_modules); static int check_laminfo(module_file_item_t *target_file, lam_list_t *dependencies, lam_list_t *found_modules); static int check_dependency(char *line, module_file_item_t *target_file, lam_list_t *dependencies, lam_list_t *found_modules); static void free_dependency_list(lam_list_t *dependencies); /* * Private variables */ static const char *laminfo_suffix = ".laminfo"; static const char *key_dependency = "dependency="; static const char module_template[] = "mca_%s_"; static lam_list_t found_files; /* * Function to find as many modules of a given type as possible. This * includes statically-linked in modules as well as opening up a * directory and looking for shared-library MCA modules of the * appropriate type (load them if available). * * Return one consolidated array of (mca_base_module_t*) pointing to all * available modules. */ int mca_base_module_find(const char *directory, const char *type, mca_base_module_t *static_modules[], lam_list_t *found_modules) { int i; mca_base_module_list_item_t *item; /* Find all the modules that were statically linked in */ lam_list_init(found_modules); for (i = 0; NULL != static_modules[i]; ++i) { item = LAM_MALLOC(sizeof(mca_base_module_list_item_t)); if (NULL == item) { return LAM_ERR_OUT_OF_RESOURCE; } lam_list_item_init((lam_list_item_t *) item); item->mli_module = static_modules[i]; lam_list_append(found_modules, (lam_list_item_t *) item); } /* Find any available dynamic modules in the specified directory */ find_dyn_modules(directory, type, NULL, found_modules); /* All done */ return LAM_SUCCESS; } /* * Open up all directories in a given path and search for modules of * the specified type (and possibly of a given name). * * Note that we use our own path iteration functionality (vs. ltdl's * lt_dladdsearchdir() functionality) because we need to look at * companion .laminfo files in the same directory as the library to * generate dependencies, etc. If we use the plain lt_dlopen() * functionality, we would not get the directory name of the file * finally opened in recursive dependency traversals. */ static void find_dyn_modules(const char *path, const char *type, const char *name, lam_list_t *found_modules) { ltfn_data_holder_t params; char *path_to_use, *dir, *end; module_file_item_t *file; lam_list_item_t *cur; strcpy(params.type, type); strcpy(params.name, name); if (NULL == name) { lam_output_verbose(0, 40, " looking for all dynamic %s MCA modules", type, NULL); } else { lam_output_verbose(0, 40, " looking for dynamic %s MCA module named \"%s\"", type, name, NULL); } /* If directory is NULL, iterate over the set of directories specified by the MCA param mca_base_module_path. If path is not NULL, then use that as the path. */ if (NULL == path) { mca_base_param_lookup_string(mca_base_param_module_path, &dir); path_to_use = strdup(dir); } if (NULL == path) { path_to_use = strdup(path); } /* Iterate over all the files in the directories in the path and make a master array of all the matching filenames that we find. */ lam_list_init(&found_files); dir = path_to_use; do { end = strchr(dir, ':'); if (NULL != end) { *end = '\0'; } if (0 != lt_dlforeachfile(dir, save_filename, ¶ms)) { break; } dir = end + 1; } while (NULL != end); /* Iterate through all the filenames that we found. Since one module may [try to] call another to be loaded, only try to load the UNVISITED files. Also, ignore the return code -- basically, give every file one chance to try to load. If they load, great. If not, great. */ for (cur = lam_list_get_first(&found_files); lam_list_get_end(&found_files) != cur; cur = lam_list_get_next(cur)) { file = (module_file_item_t *) cur; if (UNVISITED == file->status) open_module(file, found_modules); } /* So now we have a final list of loaded modules. We can free all the file information. */ for (cur = lam_list_get_first(&found_files); lam_list_get_end(&found_files) != cur; ) { file = (module_file_item_t *) cur; cur = lam_list_get_next(cur); LAM_FREE(file); lam_list_remove_first(&found_files); } /* All done */ lam_list_destroy(&found_files); LAM_FREE(path_to_use); } /* * Given a filename, see if it appears to be of the proper filename * format. If so, save it in the array so that we can process it * later. */ static int save_filename(const char *filename, lt_ptr data) { int len, prefix_len, total_len; char *prefix; const char *basename; module_file_item_t *module_file; ltfn_data_holder_t *params = (ltfn_data_holder_t *) data; /* Check to see if the file is named what we expect it to be named */ len = sizeof(module_template) + strlen(params->type) + 32; if (NULL != params->name) { len += strlen(params->name); } prefix = LAM_MALLOC(len); snprintf(prefix, len, module_template, params->type); prefix_len = strlen(prefix); if (NULL != params->name) { strcat(prefix, params->name); } total_len = strlen(prefix); basename = strrchr(filename, '/'); if (NULL == basename) { basename = filename; } else { basename += 1; } if (0 != strncmp(basename, prefix, total_len)) { LAM_FREE(prefix); return 0; } /* Save all the info and put it in the list of found modules */ module_file = LAM_MALLOC(sizeof(module_file_item_t)); if (NULL == module_file) { return LAM_ERR_OUT_OF_RESOURCE; } lam_list_item_init((lam_list_item_t *) module_file); strcpy(module_file->type, params->type); strcpy(module_file->name, basename + prefix_len); strcpy(module_file->basename, basename); strcpy(module_file->filename, filename); module_file->status = UNVISITED; lam_list_append(&found_files, (lam_list_item_t *) module_file); /* All done */ LAM_FREE(prefix); return 0; } /* * Open a module, chasing down its dependencies first, if possible. */ static int open_module(module_file_item_t *target_file, lam_list_t *found_modules) { int len; lt_dlhandle module_handle; mca_base_module_t *module_struct; char *struct_name; lam_list_t dependencies; lam_list_item_t *cur; mca_base_module_list_item_t *mitem; dependency_item_t *ditem; lam_output_verbose(0, 40, " examining dyanmic %s MCA module \"%s\"", target_file->type, target_file->name, NULL); lam_output_verbose(0, 40, " %s", target_file->filename, NULL); /* Was this module already loaded (e.g., via dependency)? */ if (LOADED == target_file->status) { lam_output_verbose(0, 40, " already loaded (ignored)", NULL); return LAM_SUCCESS; } /* Ensure that this module is not already loaded (should only happen if it was statically loaded). It's an error if it's already loaded because we're evaluating this file -- not this module. Hence, returning LAM_ERR_PARAM indicates that the *file* failed to load, not the module. */ for (cur = lam_list_get_first(found_modules); lam_list_get_end(found_modules) != cur; cur = lam_list_get_next(cur)) { mitem = (mca_base_module_list_item_t *) cur; if (0 == strcmp(mitem->mli_module->mca_type_name, target_file->type) && 0 == strcmp(mitem->mli_module->mca_module_name, target_file->name)) { lam_output_verbose(0, 40, " already loaded (ignored)", NULL); target_file->status = FAILED_TO_LOAD; return LAM_ERR_BAD_PARAM; } } /* Look at see if this module has any dependencies. If so, load them. If we can't load them, then this module must also fail to load. */ lam_list_init(&dependencies); if (0 != check_laminfo(target_file, &dependencies, found_modules)) { target_file->status = FAILED_TO_LOAD; free_dependency_list(&dependencies); return LAM_ERR_OUT_OF_RESOURCE; } /* Now try to load the module */ module_handle = lt_dlopenext(target_file->filename); if (NULL == module_handle) { lam_output_verbose(0, 40, " unable to open: %s (ignored)", lt_dlerror(), NULL); target_file->status = FAILED_TO_LOAD; free_dependency_list(&dependencies); return LAM_ERR_BAD_PARAM; } /* Successfully opened the module; now find the public struct. Malloc out enough space for it. */ len = strlen(target_file->type) + strlen(target_file->name) + 32; struct_name = LAM_MALLOC(len); if (NULL == struct_name) { lt_dlclose(module_handle); target_file->status = FAILED_TO_LOAD; free_dependency_list(&dependencies); return LAM_ERR_OUT_OF_RESOURCE; } snprintf(struct_name, len, "mca_%s_%s_module", target_file->type, target_file->name); mitem = LAM_MALLOC(sizeof(mca_base_module_list_item_t)); if (NULL == mitem) { LAM_FREE(struct_name); lt_dlclose(module_handle); target_file->status = FAILED_TO_LOAD; free_dependency_list(&dependencies); return LAM_ERR_OUT_OF_RESOURCE; } lam_list_item_init((lam_list_item_t *) mitem); module_struct = lt_dlsym(module_handle, struct_name); if (NULL == module_struct) { lam_output_verbose(0, 40, " \"%s\" does not appear to be a valid " "%s MCA dynamic module (ignored)", target_file->basename, target_file->type, NULL); LAM_FREE(mitem); LAM_FREE(struct_name); lt_dlclose(module_handle); target_file->status = FAILED_TO_LOAD; free_dependency_list(&dependencies); return LAM_ERR_BAD_PARAM; } /* We found the public struct. Save it, and register this module to be closed later. */ mitem->mli_module = module_struct; lam_list_append(found_modules, (lam_list_item_t *) mitem); mca_base_module_registry_retain(target_file->type, module_handle, module_struct); /* Now that that's all done, link all the dependencies in to this module's registry entry */ for (cur = lam_list_remove_first(&dependencies); NULL != cur; cur = lam_list_remove_first(&dependencies)) { ditem = (dependency_item_t *) cur; mca_base_module_registry_link(target_file->type, target_file->name, ditem->di_module_file_item->type, ditem->di_module_file_item->name); LAM_FREE(ditem); } lam_list_destroy(&dependencies); lam_output_verbose(0, 40, " opened dynamic %s MCA module \"%s\"", target_file->type, target_file->name, NULL); target_file->status = LOADED; /* All done */ LAM_FREE(struct_name); return LAM_SUCCESS; } /* * For a given filename, see if there exists a filename.laminfo, which * lists dependencies that must be loaded before this module is * loaded. If we find this file, try to load those modules first. * * Detect dependency cycles and error out. */ static int check_laminfo(module_file_item_t *target_file, lam_list_t *dependencies, lam_list_t *found_modules) { int len; FILE *fp; char *depname; char buffer[BUFSIZ], *p; /* Form the filename */ len = strlen(target_file->filename) + strlen(laminfo_suffix) + 16; depname = LAM_MALLOC(len); if (NULL == depname) return LAM_ERR_OUT_OF_RESOURCE; snprintf(depname, len, "%s%s", target_file->filename, laminfo_suffix); /* Try to open the file. If there's no file, return success (i.e., there are no dependencies). */ if (NULL == (fp = fopen(depname, "r"))) { LAM_FREE(depname); return 0; } /* Otherwise, loop reading the lines in the file and trying to load them. Return failure upon the first module that fails to load. */ lam_output_verbose(0, 40, " opening laminfo file: %s", depname, NULL); while (NULL != fgets(buffer, BUFSIZ, fp)) { /* Perl chomp */ buffer[BUFSIZ - 1] = '\0'; len = strlen(buffer); if ('\n' == buffer[len - 1]) buffer[len - 1] = '\0'; /* Ignore emtpy lines and lines beginning with "#" or "//" */ for (p = buffer; '\0' != p; ++p) if (!isspace(*p)) break; if ('\0' == *p) continue; else if (*p == '#' || ('/' == *p && '/' == *(p + 1))) continue; /* Is it a dependency? */ else if (0 == strncasecmp(p, key_dependency, strlen(key_dependency))) { if (LAM_SUCCESS != check_dependency(p + strlen(key_dependency), target_file, dependencies, found_modules)) { fclose(fp); LAM_FREE(depname); /* We can leave any successfully loaded dependencies; we might need them again later. But free the dependency list for this module, because since [at least] one of them didn't load, we have to pretend like all of them didn't load and disallow loading this module. So free the dependency list. */ free_dependency_list(dependencies); return LAM_ERR_OUT_OF_RESOURCE; } } } lam_output_verbose(0, 40, " laminfo file closed (%s)", target_file->basename, NULL); /* All done -- all depenencies satisfied */ fclose(fp); LAM_FREE(depname); return 0; } /* * A DEPENDENCY key was found in the laminfo file. Chase it down: see * if we've already got such a module loaded, or go try to load it if * it's not already loaded. */ static int check_dependency(char *line, module_file_item_t *target_file, lam_list_t *dependencies, lam_list_t *found_modules) { bool happiness; char buffer[BUFSIZ]; char *type, *name; module_file_item_t *mitem; dependency_item_t *ditem; lam_list_item_t *cur; /* Ensure that this was a valid dependency statement */ type = line; name = strchr(line, ':'); if (NULL == name) return LAM_ERR_OUT_OF_RESOURCE; *name = '\0'; ++name; /* Form the name of the module to compare to */ if (strlen(type) + strlen(name) + 32 >= BUFSIZ) { target_file->status = FAILED_TO_LOAD; return LAM_ERR_OUT_OF_RESOURCE; } snprintf(buffer, BUFSIZ, module_template, type); strcat(buffer, name); /* Traverse down the list of files that we have, and see if we can find it */ target_file->status = CHECKING_CYCLE; for (happiness = false, cur = lam_list_get_first(&found_files); lam_list_get_last(&found_files) != cur; cur = lam_list_get_next(cur)) { mitem = (module_file_item_t *) cur; /* Compare the name to the basename */ if (0 != strcmp(mitem->basename, buffer)) continue; /* Catch the bozo dependency on itself */ else if (mitem == target_file) { lam_output_verbose(0, 40, " module depends on itself (ignored dependency)", NULL); happiness = true; break; } /* If it's loaded, great -- we're done (no need to check that dependency sub-tree) */ else if (LOADED == mitem->status) { lam_output_verbose(0, 40, " dependency has already been loaded (%s)", mitem->basename, NULL); happiness = true; break; } /* If it's specifically not loaded (i.e., there was some kind of error when we tried to load it), then we cannot meet the dependencies. */ else if (FAILED_TO_LOAD == mitem->status) { lam_output_verbose(0, 40, " dependency previously failed to load (%s)", mitem->basename, NULL); break; } /* If we hit a cycle, return badness */ else if (CHECKING_CYCLE == mitem->status) { lam_output_verbose(0, 40, " found cycle! (%s)", mitem->basename, NULL); break; } /* Otherwise, this dependency has not been looked at yet. Go try to load it. */ else if (UNVISITED == mitem->status) { lam_output_verbose(0, 40, " loading dependency (%s)", mitem->basename, NULL); if (LAM_SUCCESS == open_module(target_file, found_modules)) { happiness = true; } else { lam_output_verbose(0, 40, " dependency failed to load (%s)", mitem->basename, NULL); } break; } } /* Did we find the dependency? */ if (!happiness) { target_file->status = FAILED_TO_LOAD; return LAM_ERR_BAD_PARAM; } /* The dependency loaded properly. Increment its refcount so that it doesn't get unloaded before we get unloaded. */ ditem = LAM_MALLOC(sizeof(dependency_item_t)); if (NULL == ditem) { return LAM_ERR_OUT_OF_RESOURCE; } cur = (lam_list_item_t *) ditem; lam_list_item_init(cur); lam_list_append(dependencies, cur); /* All done -- all depenencies satisfied */ return LAM_SUCCESS; } /* * Free a dependency list */ static void free_dependency_list(lam_list_t *dependencies) { lam_list_item_t *item; for (item = lam_list_remove_first(dependencies); NULL != item; item = lam_list_remove_first(dependencies)) { LAM_FREE(item); } lam_list_destroy(dependencies); }