From: Ian Kent <ik...@redhat.com> Persistent use of process information is needed for contained execution in a namespace other than the root init namespace.
Use a simple random token as a key to create and store thread information in a hashed list for use by the usermode helper thread runner. Signed-off-by: Ian Kent <ik...@redhat.com> Cc: Benjamin Coddington <bcodd...@redhat.com> Cc: Al Viro <v...@zeniv.linux.org.uk> Cc: J. Bruce Fields <bfie...@fieldses.org> Cc: David Howells <dhowe...@redhat.com> Cc: Trond Myklebust <trond.mykleb...@primarydata.com> Cc: Oleg Nesterov <onest...@redhat.com> Cc: Eric W. Biederman <ebied...@xmission.com> Cc: Jeff Layton <jeff.lay...@primarydata.com> --- include/linux/kmod.h | 3 + kernel/kmod.c | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 1 deletion(-) diff --git a/include/linux/kmod.h b/include/linux/kmod.h index 0555cc6..fa46722 100644 --- a/include/linux/kmod.h +++ b/include/linux/kmod.h @@ -66,6 +66,9 @@ struct subprocess_info { void *data; }; +extern int umh_wq_get_token(int token, const char *service); +extern void umh_wq_put_token(int token); + extern int call_usermodehelper(char *path, char **argv, char **envp, int wait); diff --git a/kernel/kmod.c b/kernel/kmod.c index 2777f40..55d20ce 100644 --- a/kernel/kmod.c +++ b/kernel/kmod.c @@ -40,13 +40,30 @@ #include <linux/ptrace.h> #include <linux/async.h> #include <asm/uaccess.h> +#include <linux/hash.h> +#include <linux/list.h> +#include <linux/random.h> #include <trace/events/module.h> extern int max_threads; +#define KHELPER "khelper" static struct workqueue_struct *khelper_wq; +#define UMH_WQ_HASH_SHIFT 6 +#define UMH_WQ_HASH_SIZE 1 << UMH_WQ_HASH_SHIFT + +struct umh_wq_entry { + int token; + unsigned int count; + struct workqueue_struct *wq; + struct hlist_node umh_wq_hlist; +}; + +static DEFINE_SPINLOCK(umh_wq_hash_lock); +static struct hlist_head umh_wq_hash[UMH_WQ_HASH_SIZE]; + #define CAP_BSET (void *)1 #define CAP_PI (void *)2 @@ -475,6 +492,165 @@ static void helper_unlock(void) wake_up(&running_helpers_waitq); } +static void umh_wq_hash_init(void) +{ + int i; + + for (i = 0; i < UMH_WQ_HASH_SIZE; i++) + INIT_HLIST_HEAD(&umh_wq_hash[i]); +} + +static struct umh_wq_entry *umh_wq_find_entry(int token) +{ + struct umh_wq_entry *this, *entry; + struct hlist_head *bucket; + unsigned int hash; + + hash = hash_32((unsigned long) token, UMH_WQ_HASH_SHIFT); + bucket = &umh_wq_hash[hash]; + + entry = ERR_PTR(-ENOENT); + if (hlist_empty(bucket)) + goto out; + + hlist_for_each_entry(this, bucket, umh_wq_hlist) { + if (this->token == token) { + entry = this; + break; + } + } +out: + return entry; +} + +static struct workqueue_struct *umh_find_wq(int token, unsigned int nowait) +{ + struct umh_wq_entry *entry; + unsigned long flags; + + if (!token) + return khelper_wq; + + if (nowait) + spin_lock_irqsave(&umh_wq_hash_lock, flags); + else + spin_lock(&umh_wq_hash_lock); + entry = umh_wq_find_entry(token); + if (nowait) + spin_unlock_irqrestore(&umh_wq_hash_lock, flags); + else + spin_unlock(&umh_wq_hash_lock); + + return entry->wq; +} + +/** + * umh_wq_get_token - create service thread and return an identifying token + * @token: token of an existing service thread or 0 to create a new + * service thread. + * @name: service name to be appended to "khelper" for identification. + * + * Returns a token that used with calls to call_usermode_helper_service(). + * If token corresponds to an existing service thread its reference count + * is increased and the token returned. On failure returns a negative errno. + */ +int umh_wq_get_token(int token, const char *service) +{ + struct workqueue_struct *wq; + char *wq_name; + int wq_name_len; + struct umh_wq_entry *entry; + struct hlist_head *bucket; + unsigned int hash; + unsigned int new_token; + + if (token) { + spin_lock(&umh_wq_hash_lock); + entry = umh_wq_find_entry(token); + if (entry) { + entry->count++; + spin_unlock(&umh_wq_hash_lock); + return token; + } + spin_unlock(&umh_wq_hash_lock); + } + + if (!service) + return -EINVAL; + + wq_name_len = sizeof(KHELPER) + strlen(service) + 1; + wq_name = kmalloc(wq_name_len, GFP_KERNEL); + if (!wq_name) + return -ENOMEM; + strcpy(wq_name, "KHELPER-"); + strcat(wq_name, service); + + entry = kzalloc(sizeof(struct umh_wq_entry), GFP_KERNEL); + if (!entry) { + kfree(wq_name); + return -ENOMEM; + } + + wq = create_singlethread_workqueue(wq_name); + if (IS_ERR(wq)) { + kfree(wq_name); + kfree(entry); + return PTR_ERR(wq); + } + kfree(wq_name); + entry->wq = wq; + + do { + new_token = get_random_int(); + if (!new_token) + continue; + spin_lock(&umh_wq_hash_lock); + entry = umh_wq_find_entry(new_token); + if (likely(IS_ERR(entry))) { + hash = hash_32(new_token, UMH_WQ_HASH_SHIFT); + bucket = &umh_wq_hash[hash]; + hlist_add_head(&entry->umh_wq_hlist, bucket); + entry->token = (long) new_token; + spin_unlock(&umh_wq_hash_lock); + break; + } + spin_unlock(&umh_wq_hash_lock); + } while (1); + + return (int) new_token; +} +EXPORT_SYMBOL(umh_wq_get_token); + +/** + * umh_ns_put_token - stop a service thread if it's not longer in use + * and remove it from the serive thread list + * @token: token of a service thread. + */ +void umh_wq_put_token(int token) +{ + struct umh_wq_entry *entry; + + if (!token) + return; + + spin_lock(&umh_wq_hash_lock); + entry = umh_wq_find_entry(token); + if (unlikely(IS_ERR(entry))) + spin_unlock(&umh_wq_hash_lock); + else { + if (--entry->count) + spin_unlock(&umh_wq_hash_lock); + else { + hlist_del(&entry->umh_wq_hlist); + spin_unlock(&umh_wq_hash_lock); + destroy_workqueue(entry->wq); + kfree(entry); + } + } + return; +} +EXPORT_SYMBOL(umh_wq_put_token); + /** * call_usermodehelper_setup - prepare to call a usermode helper * @path: path to usermode executable @@ -689,6 +865,7 @@ struct ctl_table usermodehelper_table[] = { void __init usermodehelper_init(void) { - khelper_wq = create_singlethread_workqueue("khelper"); + umh_wq_hash_init(); + khelper_wq = create_singlethread_workqueue(KHELPER); BUG_ON(!khelper_wq); } -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/