diff options
Diffstat (limited to 'kernel/kmod.c')
-rw-r--r-- | kernel/kmod.c | 164 |
1 files changed, 150 insertions, 14 deletions
diff --git a/kernel/kmod.c b/kernel/kmod.c index 3a7379aa31ca..9f923f8ce6a0 100644 --- a/kernel/kmod.c +++ b/kernel/kmod.c @@ -36,6 +36,8 @@ #include <linux/resource.h> #include <asm/uaccess.h> +extern int delete_module(const char *name, unsigned int flags); + extern int max_threads; static struct workqueue_struct *khelper_wq; @@ -46,6 +48,7 @@ static struct workqueue_struct *khelper_wq; modprobe_path is set via /proc/sys. */ char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe"; +struct module_kobject kmod_mk; /** * request_module - try to load a kernel module @@ -75,6 +78,11 @@ int request_module(const char *fmt, ...) static atomic_t kmod_concurrent = ATOMIC_INIT(0); #define MAX_KMOD_CONCURRENT 50 /* Completely arbitrary value - KAO */ static int kmod_loop_msg; + char modalias[16 + MODULE_NAME_LEN] = "MODALIAS="; + char *uevent_envp[2] = { + modalias, + NULL + }; va_start(args, fmt); ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args); @@ -82,6 +90,12 @@ int request_module(const char *fmt, ...) if (ret >= MODULE_NAME_LEN) return -ENAMETOOLONG; + strcpy(&modalias[strlen("MODALIAS=")], module_name); + kobject_uevent_env(&kmod_mk.kobj, KOBJ_CHANGE, uevent_envp); + + if (modprobe_path[0] == '\0') + goto out; + /* If modprobe needs a service that is in a module, we get a recursive * loop. Limit the number of running kmod threads to max_threads/2 or * MAX_KMOD_CONCURRENT, whichever is the smaller. A cleaner method @@ -108,9 +122,115 @@ int request_module(const char *fmt, ...) ret = call_usermodehelper(modprobe_path, argv, envp, 1); atomic_dec(&kmod_concurrent); +out: return ret; } EXPORT_SYMBOL(request_module); + +static ssize_t store_mod_request(struct module_attribute *mattr, + struct module *mod, + const char *buffer, size_t count) +{ + char name[MODULE_NAME_LEN]; + int ret; + + if (count < 1 || count+1 > MODULE_NAME_LEN) + return -EINVAL; + memcpy(name, buffer, count); + name[count] = '\0'; + if (name[count-1] == '\n') + name[count-1] = '\0'; + + ret = request_module(name); + if (ret < 0) + return ret; + return count; +} + +static struct module_attribute mod_request = { + .attr = { .name = "mod_request", .mode = S_IWUSR, .owner = THIS_MODULE }, + .store = store_mod_request, +}; + +#ifdef CONFIG_MODULE_UNLOAD +static ssize_t store_mod_unload(struct module_attribute *mattr, + struct module *mod, + const char *buffer, size_t count) +{ + char name[MODULE_NAME_LEN]; + int ret; + + if (count < 1 || count+1 > MODULE_NAME_LEN) + return -EINVAL; + memcpy(name, buffer, count); + name[count] = '\0'; + if (name[count-1] == '\n') + name[count-1] = '\0'; + + ret = delete_module(name, O_NONBLOCK); + if (ret < 0) + return ret; + return count; +} + +static struct module_attribute mod_unload = { + .attr = { .name = "mod_unload", .mode = S_IWUSR, .owner = THIS_MODULE }, + .store = store_mod_unload, +}; +#endif + +static ssize_t show_mod_request_helper(struct module_attribute *mattr, + struct module *mod, + char *buffer) +{ + return sprintf(buffer, "%s\n", modprobe_path); +} + +static ssize_t store_mod_request_helper(struct module_attribute *mattr, + struct module *mod, + const char *buffer, size_t count) +{ + if (count < 1 || count+1 > KMOD_PATH_LEN) + return -EINVAL; + memcpy(modprobe_path, buffer, count); + modprobe_path[count] = '\0'; + if (modprobe_path[count-1] == '\n') + modprobe_path[count-1] = '\0'; + return count; +} + +static struct module_attribute mod_request_helper = { + .attr = { + .name = "mod_request_helper", + .mode = S_IWUSR | S_IRUGO, + .owner = THIS_MODULE + }, + .show = show_mod_request_helper, + .store = store_mod_request_helper, +}; + +void __init kmod_sysfs_init(void) +{ + int ret; + + kmod_mk.mod = THIS_MODULE; + kobj_set_kset_s(&kmod_mk, module_subsys); + kobject_set_name(&kmod_mk.kobj, "kmod"); + kobject_init(&kmod_mk.kobj); + ret = kobject_add(&kmod_mk.kobj); + if (ret < 0) + goto out; + + ret = sysfs_create_file(&kmod_mk.kobj, &mod_request_helper.attr); + ret = sysfs_create_file(&kmod_mk.kobj, &mod_request.attr); +#ifdef CONFIG_MODULE_UNLOAD + ret = sysfs_create_file(&kmod_mk.kobj, &mod_unload.attr); +#endif + + kobject_uevent(&kmod_mk.kobj, KOBJ_ADD); +out: + return; +} #endif /* CONFIG_KMOD */ struct subprocess_info { @@ -217,7 +337,10 @@ static int wait_for_helper(void *data) sub_info->retval = ret; } - complete(sub_info->complete); + if (sub_info->wait < 0) + kfree(sub_info); + else + complete(sub_info->complete); return 0; } @@ -239,6 +362,9 @@ static void __call_usermodehelper(struct work_struct *work) pid = kernel_thread(____call_usermodehelper, sub_info, CLONE_VFORK | SIGCHLD); + if (wait < 0) + return; + if (pid < 0) { sub_info->retval = pid; complete(sub_info->complete); @@ -253,6 +379,9 @@ static void __call_usermodehelper(struct work_struct *work) * @envp: null-terminated environment list * @session_keyring: session keyring for process (NULL for an empty keyring) * @wait: wait for the application to finish and return status. + * when -1 don't wait at all, but you get no useful error back when + * the program couldn't be exec'ed. This makes it safe to call + * from interrupt context. * * Runs a user-space application. The application is started * asynchronously if wait is not set, and runs as a child of keventd. @@ -265,17 +394,8 @@ int call_usermodehelper_keys(char *path, char **argv, char **envp, struct key *session_keyring, int wait) { DECLARE_COMPLETION_ONSTACK(done); - struct subprocess_info sub_info = { - .work = __WORK_INITIALIZER(sub_info.work, - __call_usermodehelper), - .complete = &done, - .path = path, - .argv = argv, - .envp = envp, - .ring = session_keyring, - .wait = wait, - .retval = 0, - }; + struct subprocess_info *sub_info; + int retval; if (!khelper_wq) return -EBUSY; @@ -283,9 +403,25 @@ int call_usermodehelper_keys(char *path, char **argv, char **envp, if (path[0] == '\0') return 0; - queue_work(khelper_wq, &sub_info.work); + sub_info = kzalloc(sizeof(struct subprocess_info), GFP_ATOMIC); + if (!sub_info) + return -ENOMEM; + + INIT_WORK(&sub_info->work, __call_usermodehelper); + sub_info->complete = &done; + sub_info->path = path; + sub_info->argv = argv; + sub_info->envp = envp; + sub_info->ring = session_keyring; + sub_info->wait = wait; + + queue_work(khelper_wq, &sub_info->work); + if (wait < 0) /* task has freed sub_info */ + return 0; wait_for_completion(&done); - return sub_info.retval; + retval = sub_info->retval; + kfree(sub_info); + return retval; } EXPORT_SYMBOL(call_usermodehelper_keys); |