diff -purN linux-2.6.8-rc2-mm2/kernel/kmod.c linux-2.6.8-rc2-mm2.hotplug/kernel/kmod.c --- linux-2.6.8-rc2-mm2/kernel/kmod.c 2004-08-03 14:58:15.000000000 +0200 +++ linux-2.6.8-rc2-mm2.hotplug/kernel/kmod.c 2004-08-03 15:21:35.000000000 +0200 @@ -17,6 +17,9 @@ call_usermodehelper wait flag, and remove exec_usermodehelper. Rusty Russell Jan 2003 + + resource management for call_usermodehelper + Hannes Reinecke Jul 2004 */ #define __KERNEL_SYSCALLS__ @@ -41,6 +44,8 @@ extern int max_threads; static struct workqueue_struct *khelper_wq; +int khelper_max = 50; +static struct semaphore khelper_sem; #ifdef CONFIG_KMOD @@ -67,16 +72,12 @@ int request_module(const char *fmt, ...) { va_list args; char module_name[MODULE_NAME_LEN]; - unsigned int max_modprobes; int ret; char *argv[] = { modprobe_path, "-q", "--", module_name, NULL }; static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }; - static atomic_t kmod_concurrent = ATOMIC_INIT(0); -#define MAX_KMOD_CONCURRENT 50 /* Completely arbitrary value - KAO */ - static int kmod_loop_msg; va_start(args, fmt); ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args); @@ -95,21 +96,11 @@ int request_module(const char *fmt, ...) * * "trace the ppid" is simple, but will fail if someone's * parent exits. I think this is as good as it gets. --RR + * + * Resource checking is now implemented in + * call_usermodehelper --hare */ - max_modprobes = min(max_threads/2, MAX_KMOD_CONCURRENT); - atomic_inc(&kmod_concurrent); - if (atomic_read(&kmod_concurrent) > max_modprobes) { - /* We may be blaming an innocent here, but unlikely */ - if (kmod_loop_msg++ < 5) - printk(KERN_ERR - "request_module: runaway loop modprobe %s\n", - module_name); - atomic_dec(&kmod_concurrent); - return -ENOMEM; - } - ret = call_usermodehelper(modprobe_path, argv, envp, 1); - atomic_dec(&kmod_concurrent); return ret; } EXPORT_SYMBOL(request_module); @@ -175,12 +166,98 @@ static int ____call_usermodehelper(void do_exit(0); } +/* + * Copy process args + */ +void khelper_copy_info(struct subprocess_info *orig, + struct subprocess_info *new) +{ + int ie, ia, l; + char *p; + + l = 0; + if (orig->path) + l += strlen(orig->path) + 1; + + ia = 0; + while (orig->argv[ia] && ia < 7) { + l += strlen(orig->argv[ia]) + 1; + ia++; + } + l += sizeof(char *) * (ia + 1); + ie = 0; + while (orig->envp[ie] && ie < 31) { + l += strlen(orig->envp[ie]) + 1; + ie++; + } + l += sizeof(char *) * (ie + 1); + + new->path = kmalloc(l, GFP_KERNEL); + if (!new->path) + return; + memset(new->path, 0, l); + p = new->path; + strcpy(p, orig->path); + p += strlen(p); + p++; + new->argv = (char **)p; + p += sizeof(char *) * (ia + 1); + + ia = 0; + while (orig->argv[ia] && ia < 7) { + strcpy(p, orig->argv[ia]); + new->argv[ia] = p; + p += strlen(p); + *p++ = '\0'; + ia++; + } + new->argv[ia] = NULL; + + new->envp = (char **)p; + p += sizeof(char *) * (ie + 1); + + ie = 0; + while (orig->envp[ie] && ie < 31) { + strcpy(p, orig->envp[ie]); + new->envp[ie] = p; + p += strlen(p); + *p++ = '\0'; + ie++; + } + new->envp[ie] = NULL; +} + /* Keventd can't block, but this (a child) can. */ static int wait_for_helper(void *data) { - struct subprocess_info *sub_info = data; + struct subprocess_info *sub_info = data, stored_info; pid_t pid; struct k_sigaction sa; + int flags = SIGCHLD, retval, wait = sub_info->wait; + + /* Copy process info, it's not vaild after complete() */ + khelper_copy_info(sub_info, &stored_info); + if (!stored_info.path) { + sub_info->retval = -ENOMEM; + complete(sub_info->complete); + return 0; + } + + if (down_trylock(&khelper_sem)) { + /* + * We have exceeded the maximum number of + * concurrent kmod invocations. Delay this process + * until enough resources are available again. + */ + if (wait == 0) { + /* Queueing is for async events only */ + wait = -1; + sub_info->retval = -EAGAIN; + complete(sub_info->complete); + } + down(&khelper_sem); + } + /* Do the real action */ /* Install a handler: if SIGCLD isn't handled sys_wait4 won't * populate the status, but will return -ECHILD. */ @@ -190,23 +267,62 @@ static int wait_for_helper(void *data) do_sigaction(SIGCHLD, &sa, (struct k_sigaction *)0); allow_signal(SIGCHLD); - pid = kernel_thread(____call_usermodehelper, sub_info, SIGCHLD); + if (wait < 1) { + /* CLONE_VFORK: wait until the usermode helper has execve'd + * successfully We need the data structures to stay around + * until that is done. */ + flags |= CLONE_VFORK; + } + + pid = kernel_thread(____call_usermodehelper, &stored_info, flags); if (pid < 0) { - sub_info->retval = pid; - } else { + if (wait >= 0) { + sub_info->retval = pid; + complete(sub_info->complete); + } + kfree(stored_info.path); + /* Bail out */ + up(&khelper_sem); + + return 0; + } + /* + * usermodehelper started successfully + * We always block for the child to exit as we want to + * keep track of the number of currently running processes. + */ + + if (wait == 0) { /* - * Normally it is bogus to call wait4() from in-kernel because - * wait4() wants to write the exit code to a userspace address. - * But wait_for_helper() always runs as keventd, and put_user() - * to a kernel address works OK for kernel threads, due to their - * having an mm_segment_t which spans the entire address space. - * - * Thus the __user pointer cast is valid here. + * For asynchronous events notify the caller + * immediately, but wait for the event to finish. */ - sys_wait4(pid, (int __user *) &sub_info->retval, 0, NULL); + complete(sub_info->complete); } - complete(sub_info->complete); + /* + * Normally it is bogus to call wait4() from in-kernel because + * wait4() wants to write the exit code to a userspace address. + * But wait_for_helper() always runs as keventd, and put_user() + * to a kernel address works OK for kernel threads, due to their + * having an mm_segment_t which spans the entire address space. + * + * Thus the __user pointer cast is valid here. + */ + sys_wait4(pid, (int __user *) &retval, 0, NULL); + + if (wait > 0) { + /* + * For synchronous events we can return the exit + * status of the child. + */ + sub_info->retval = retval; + complete(sub_info->complete); + } + + kfree(stored_info.path); + up(&khelper_sem); + return 0; } @@ -216,21 +332,13 @@ static void __call_usermodehelper(void * struct subprocess_info *sub_info = data; pid_t pid; - /* CLONE_VFORK: wait until the usermode helper has execve'd - * successfully We need the data structures to stay around - * until that is done. */ - if (sub_info->wait) - pid = kernel_thread(wait_for_helper, sub_info, - CLONE_FS | CLONE_FILES | SIGCHLD); - else - pid = kernel_thread(____call_usermodehelper, sub_info, - CLONE_VFORK | SIGCHLD); + pid = kernel_thread(wait_for_helper, sub_info, + CLONE_FS | CLONE_FILES | SIGCHLD); if (pid < 0) { sub_info->retval = pid; complete(sub_info->complete); - } else if (!sub_info->wait) - complete(sub_info->complete); + } } /** @@ -276,4 +384,30 @@ void __init usermodehelper_init(void) { khelper_wq = create_singlethread_workqueue("khelper"); BUG_ON(!khelper_wq); + + /* + * Limit the max number of concurrent processes + * to something sane; 10 - max_threads/2 seems ok. + */ + if (khelper_max > max_threads/2) + khelper_max = max_threads/2; + if (khelper_max < 10) + khelper_max = 10; + + printk(KERN_INFO "khelper: max %d concurrent processes\n", + khelper_max); + sema_init(&khelper_sem, khelper_max); +} + +/* + * Sanity check for khelper_max is done in usermodehelper_init, + * as at this point of time the system is not fully initialised. + */ +static int __init khelper_setup(char *s) +{ + get_option(&s, &khelper_max); + + return 1; } +__setup("khelper_max=", khelper_setup); +