Name: Fix races with request_module vs. loads and unloads Author: Rusty Russell Status: Experimental Depends: Module/locking.patch.gz D: There is a race with request_module against a other module D: operations (ie. a system administrator removing a module will cause D: a request_module to fail if timed correctly). Also, two D: request_module() could clash: the second one might fail and return D: before the first has completed, leading to a spurious failure. D: D: These are solved by having a waitqueue for waiting on modules which D: are loading or going away, which is done during init. D: D: We do *not* wait for modules which are being removed with rmmod D: --wait, in the assumption that the administrator won't appreciate D: us reinserting the module they are trying so hard to remove (and D: also, it can take a long time). diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .19975-linux-2.5-bk/include/linux/module.h .19975-linux-2.5-bk.updated/include/linux/module.h --- .19975-linux-2.5-bk/include/linux/module.h 2003-01-12 18:45:51.000000000 +1100 +++ .19975-linux-2.5-bk.updated/include/linux/module.h 2003-01-12 18:49:19.000000000 +1100 @@ -135,6 +135,7 @@ enum module_state MODULE_STATE_COMING, /* Inside module_init function */ MODULE_STATE_GOING, /* Inside module_exit function */ MODULE_STATE_GOING_SLOWLY, /* Waiting for refcount to hit zero */ + MODULE_STATE_DEAD, /* You should never see this */ }; struct module diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .19975-linux-2.5-bk/kernel/module.c .19975-linux-2.5-bk.updated/kernel/module.c --- .19975-linux-2.5-bk/kernel/module.c 2003-01-12 18:45:51.000000000 +1100 +++ .19975-linux-2.5-bk.updated/kernel/module.c 2003-01-12 18:48:55.000000000 +1100 @@ -51,10 +51,13 @@ #define symbol_is(literal, string) \ (strcmp(MODULE_SYMBOL_PREFIX literal, (string)) == 0) -/* Protects module list */ +/* Protects module list, and modulestate wait queue. */ static spinlock_t modlist_lock = SPIN_LOCK_UNLOCKED; static LIST_HEAD(modules); +/* Waitqueue for those waiting on any module to change state. */ +static DECLARE_WAIT_QUEUE_HEAD(modulestate); + /* We require a truly strong try_module_get() */ static inline int strong_try_module_get(struct module *mod) { @@ -141,6 +144,12 @@ static struct module *find_module(const return NULL; } +static void set_module_state(struct module *mod, enum module_state newstate) +{ + mod->state = newstate; + wake_up_all(&modulestate); +} + #ifdef CONFIG_MODULE_UNLOAD /* Init the unload section of the module. */ static void module_unload_init(struct module *mod) @@ -469,9 +478,9 @@ sys_delete_module(const char *name_user, } mod->waiter = current; if (flags & O_NONBLOCK) - mod->state = MODULE_STATE_GOING; + set_module_state(mod, MODULE_STATE_GOING); else - mod->state = MODULE_STATE_GOING_SLOWLY; + set_module_state(mod, MODULE_STATE_GOING_SLOWLY); spin_unlock(&modlist_lock); restart_refcounts(); @@ -775,6 +784,9 @@ static void free_module(struct module *m spin_lock_irq(&modlist_lock); list_del(&mod->list); spin_unlock_irq(&modlist_lock); + + /* Wake up anyone waiting for this to go. */ + module_set_state(mod, MODULE_STATE_DEAD); __free_module(mod); } @@ -1256,9 +1268,30 @@ static struct module *load_module(void * /* We want to load this module: drop lock and wait if neccessary. */ static int module_already_exists(const char *name) { - if (find_module(name)) + struct module *mod; + wait_queue_t wait; + + again: + mod = find_module(name); + if (!mod) + return 0; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&modulestate, &wait); + + if (mod->state == MODULE_STATE_LIVE + || mod->state == MODULE_STATE_GOING_SLOWLY) { + remove_wait_queue(&wait, current); return 1; - return 0; + } + + /* OK. It's either coming, in which case we should fail after + it's loaded (imagine two request_module() racing), or + going, so we should wait so we can load it properly. */ + spin_unlock(&modlist_lock); + schedule(); + spin_lock(&modlist_lock); + goto again; } /* This is where the real work happens */ @@ -1303,7 +1336,7 @@ sys_init_module(void *umod, if (ret < 0) { /* Init routine failed: abort. Try to protect us from buggy refcounters. */ - mod->state = MODULE_STATE_GOING; + set_module_state(mod, MODULE_STATE_GOING); synchronize_kernel(); if (mod->unsafe) printk(KERN_ERR "%s: module is now stuck!\n", @@ -1316,7 +1349,7 @@ sys_init_module(void *umod, /* Now it's a first class citizen! */ module_free(mod, mod->module_init); spin_lock_irq(&modlist_lock); - mod->state = MODULE_STATE_LIVE; + set_module_state(mod, MODULE_STATE_LIVE); mod->module_init = NULL; mod->init_size = 0; spin_unlock_irq(&modlist_lock);