/* * modules.c - module loading functionality * * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * 2008, 2009 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author(s): Erik Troan * Matt Wilson * Michael Fulbright * Jeremy Katz * David Cantrell */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../pyanaconda/isys/log.h" #include "loader.h" #include "modules.h" #include "readvars.h" /* boot flags */ extern uint64_t flags; static GSList *modopts = NULL; static GSList *blacklist = NULL; static gboolean _isValidModule(gchar *module) { gint fd = -1, i = 0; gchar *path = NULL, *buf = NULL, *modname = NULL; gchar *ends[] = { ".ko.gz:", ".ko:", NULL }; struct utsname utsbuf; struct stat sbuf; if (uname(&utsbuf) == -1) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); return FALSE; } if (asprintf(&path, "/lib/modules/%s/modules.dep", utsbuf.release) == -1) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); return FALSE; } if (stat(path, &sbuf) == -1) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); free(path); return FALSE; } if ((fd = open(path, O_RDONLY)) == -1) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); free(path); return FALSE; } else { free(path); } buf = mmap(0, sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); if (!buf || buf == MAP_FAILED) { close(fd); return FALSE; } close(fd); while (ends[i] != NULL) { if (asprintf(&modname, "/%s%s", module, ends[i]) == -1) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); return FALSE; } if (g_strstr_len(buf, -1, modname) != NULL) { munmap(buf, sbuf.st_size); free(modname); return TRUE; } free(modname); modname = NULL; i++; } munmap(buf, sbuf.st_size); return FALSE; } static void _addOption(const gchar *module, const gchar *option) { gboolean found = FALSE; GSList *iterator = modopts; module_t *modopt = NULL; gchar *tmpopt = g_strdup(option); while (iterator != NULL) { modopt = (module_t *) iterator->data; if (!strncmp(modopt->name, module, strlen(modopt->name))) { found = TRUE; break; } iterator = g_slist_next(iterator); } if (found) { modopt->options = g_slist_append(modopt->options, tmpopt); } else { if ((modopt = g_malloc0(sizeof(module_t))) == NULL) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); abort(); } modopt->name = g_strdup(module); modopt->options = NULL; modopt->options = g_slist_append(modopt->options, tmpopt); modopts = g_slist_append(modopts, modopt); } return; } static gboolean _writeModulesConf(gchar *conf) { gint fd = -1, rc = 0, len = 0; GSList *iterator = modopts; GString *buf = g_string_new("# Module options and blacklists written by anaconda\n"); if (conf == NULL) { /* XXX: should this use mkstemp() ? */ conf = "/tmp/modprobe.conf"; } if ((fd = open(conf, O_WRONLY | O_CREAT, 0644)) == -1) { logMessage(ERROR, "error opening to %s: %m", conf); return FALSE; } while (iterator != NULL) { module_t *modopt = iterator->data; GSList *optiterator = modopt->options; g_string_append_printf(buf, "options %s", modopt->name); while (optiterator != NULL) { gchar *option = (gchar *) optiterator->data; g_string_append_printf(buf, " %s", option); optiterator = g_slist_next(optiterator); } g_string_append(buf, "\n"); iterator = g_slist_next(iterator); } iterator = blacklist; while (iterator != NULL) { gchar *module = (gchar *) iterator->data; g_string_append_printf(buf, "blacklist %s\n", module); iterator = g_slist_next(iterator); } len = buf->len; rc = write(fd, buf->str, len); close(fd); g_string_free(buf, TRUE); return (rc == len); } static gboolean _doLoadModule(const gchar *module, gchar **args) { gint child; gint status; if (!(child = fork())) { gint i, rc; gchar **argv = NULL; gint fd = -1; if ((argv = g_malloc0(3 * sizeof(*argv))) == NULL) { if (loggingReady()) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); } abort(); } if ((fd = open("/dev/tty3", O_RDWR)) == -1) { if (loggingReady()) { logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); } } else { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } argv[0] = "/sbin/modprobe"; argv[1] = g_strdup(module); argv[2] = NULL; if (args) { for (i = 0; args[i] ; i++) { _addOption(module, args[i]); } _writeModulesConf(MODULES_CONF); } rc = execv("/sbin/modprobe", argv); g_strfreev(argv); _exit(rc); } waitpid(child, &status, 0); if (!WIFEXITED(status) || (WIFEXITED(status) && WEXITSTATUS(status))) { return TRUE; } else { return FALSE; } } gboolean mlInitModuleConfig(void) { GHashTableIter iter; gpointer key = NULL, value = NULL; GHashTable *cmdline = readvars_parse_file("/proc/cmdline"); if (cmdline == NULL) { return _writeModulesConf(MODULES_CONF); } g_hash_table_iter_init(&iter, cmdline); while (g_hash_table_iter_next(&iter, &key, &value)) { gchar *k = (gchar *) key; gchar *v = (gchar *) value; if (v == NULL) { continue; } else if (!strcasecmp(k, "blacklist")) { gchar *tmpmod = g_strdup(v); blacklist = g_slist_append(blacklist, tmpmod); } else if (!strstr(k, ".")) { gchar **fields = g_strsplit(k, ".", 0); if (g_strv_length(fields) == 2 && _isValidModule(fields[0])) { GString *tmp = g_string_new(fields[1]); g_string_append_printf(tmp, "=%s", v); _addOption(fields[0], tmp->str); g_string_free(tmp, TRUE); } g_strfreev(fields); } } g_hash_table_destroy(cmdline); return _writeModulesConf(MODULES_CONF); } /* load a module with a given list of arguments */ gboolean mlLoadModule(const gchar *module, gchar **args) { return _doLoadModule(module, args); } /* loads a : separated list of modules */ gboolean mlLoadModuleSet(const gchar *modNames) { gchar **mods = NULL, **iterator = NULL; gboolean rc = FALSE; if (modNames == NULL) { return FALSE; } if ((mods = g_strsplit(modNames, ":", 0)) != NULL) { iterator = mods; while (*iterator != NULL) { rc |= _doLoadModule(*iterator, NULL); iterator++; } } else { return FALSE; } g_strfreev(mods); return rc; } gboolean mlAddBlacklist(gchar *module) { gchar *tmpmod = NULL; if (module == NULL) { return FALSE; } tmpmod = g_strdup(module); blacklist = g_slist_append(blacklist, tmpmod); return _writeModulesConf(MODULES_CONF); } gboolean mlRemoveBlacklist(gchar *module) { GSList *iterator = blacklist; if (module == NULL) { return FALSE; } while (iterator != NULL) { if (!strcmp((gchar *) iterator->data, module)) { iterator = g_slist_delete_link(blacklist, iterator); continue; } else { iterator = g_slist_next(iterator); } } return TRUE; } inline gint gcmp(gconstpointer a, gconstpointer b, gpointer userptr) { return g_strcmp0(a, b); } int processModuleLines(int (*f)(gchar**, void*), void *data) { char *line = NULL; size_t linesize = 0; gchar** lineparts = NULL; int count = 0; FILE *file = fopen("/proc/modules", "r"); if (file == NULL) return -1; while (1) { if (getline(&line, &linesize, file) < 0) break; if (line == NULL) break; lineparts = g_strsplit_set(line, " ", 4); free(line); line = NULL; int ret = f(lineparts, data); g_strfreev(lineparts); if (ret < 0) break; count+=ret; } fclose(file); return count; } inline int cb_savestate(gchar** parts, void *data0) { GTree *data = data0; logMessage(DEBUGLVL, "Saving module %s", parts[0]); g_tree_insert(data, g_strdup(parts[0]), (gchar*)1); return 1; } GTree* mlSaveModuleState() { GTree *state = NULL; state = g_tree_new_full(gcmp, NULL, g_free, NULL); if(!state) return NULL; processModuleLines(cb_savestate, state); return state; } inline int cb_restorestate(gchar** parts, void *data0) { GTree *data = data0; pid_t pid; int status; /* this module has to stay loaded */ if (g_tree_lookup(data, parts[0])){ return 0; } /* this module is still required */ if (parts[2][0] != '0') { return 0; } /* rmmod parts[0] */ pid = fork(); if (pid == 0) { execl("/sbin/rmmod", "-f", parts[0], NULL); _exit(1); } else if (pid < 0) { logMessage(ERROR, "Module %s removal FAILED", parts[0]); return 0; } waitpid(pid, &status, 0); if (WEXITSTATUS(status)) { logMessage(DEBUGLVL, "Module %s was NOT removed", parts[0]); return 0; } else{ logMessage(DEBUGLVL, "Module %s was removed", parts[0]); return 1; } return 0; } void mlRestoreModuleState(GTree *state) { if(!state) return; logMessage(INFO, "Restoring module state..."); /* repeat until we can't remove anything else */ while (processModuleLines(cb_restorestate, state) > 0) /* noop */; } void mlFreeModuleState(GTree *state) { if(!state) return; g_tree_destroy(state); } inline int cb_saveversions(gchar** parts, void *data0) { GHashTable *ht = data0; gchar *module = g_strdup(parts[0]); char *versionfilename; char *srcversionfilename; gchar *version; gchar *srcversion; gchar *value, *value2; checked_asprintf(&versionfilename, "/sys/module/%s/version", module); checked_asprintf(&srcversionfilename, "/sys/module/%s/srcversion", module); /* emty string */ value = g_new0(gchar, 1); /* get possible version file */ if (g_file_get_contents(versionfilename, &version, NULL, NULL)) { value2 = g_strconcat(value, version, "/", NULL); g_free(value); g_free(version); value = value2; } /* get possible src version file */ if (g_file_get_contents(srcversionfilename, &srcversion, NULL, NULL)) { value2 = g_strconcat(value, srcversion, NULL); g_free(value); g_free(srcversion); value = value2; } free(versionfilename); free(srcversionfilename); g_hash_table_insert(ht, module, value); return 1; } VersionState mlVersions() { GHashTable *ht = NULL; ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); if(!ht) return NULL; /* read version info about all modules */ processModuleLines(cb_saveversions, ht); return (VersionState)ht; } void mlFreeVersions(VersionState ht) { g_hash_table_destroy((GHashTable*)ht); } int mlDetectUpdate(VersionState a, VersionState b) { int rc = 0; if(!a && !b) return 0; if(!a) return 1; if(!b) return 1; GList *modules = g_hash_table_get_keys(b); if(!modules) return 0; GList *iter = modules; while (iter && !rc) { gchar *va = g_hash_table_lookup(a, iter->data); gchar *vb = g_hash_table_lookup(b, iter->data); if (!va) rc = 1; // new module else rc = strcmp(va, vb); // check versions for match iter = iter->next; } g_list_free(modules); return abs(rc); }