X-Git-Url: http://ftp.safe.ca/?p=safe%2Fjmp%2Flinux-2.6;a=blobdiff_plain;f=lib%2Fkobject_uevent.c;h=59c15511d58ab9da74ed6d77fed198b666235c6b;hp=1ede5aa33376cbbf273a17a7fcbb0b6b7d1c77f3;hb=b2e75eff5e859d0c294e7405958362b26a423c6e;hpb=eb11d8ffceead1eb3d84366f1687daf2217e883e diff --git a/lib/kobject_uevent.c b/lib/kobject_uevent.c index 1ede5aa..59c1551 100644 --- a/lib/kobject_uevent.c +++ b/lib/kobject_uevent.c @@ -15,355 +15,408 @@ */ #include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include #include +#include -#define BUFFER_SIZE 1024 /* buffer for the hotplug env */ -#define NUM_ENVP 32 /* number of env pointers */ -#if defined(CONFIG_KOBJECT_UEVENT) || defined(CONFIG_HOTPLUG) -static char *action_to_string(enum kobject_action action) -{ - switch (action) { - case KOBJ_ADD: - return "add"; - case KOBJ_REMOVE: - return "remove"; - case KOBJ_CHANGE: - return "change"; - case KOBJ_MOUNT: - return "mount"; - case KOBJ_UMOUNT: - return "umount"; - case KOBJ_OFFLINE: - return "offline"; - case KOBJ_ONLINE: - return "online"; - default: - return NULL; - } -} +u64 uevent_seqnum; +char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH; +static DEFINE_SPINLOCK(sequence_lock); +#ifdef CONFIG_NET +struct uevent_sock { + struct list_head list; + struct sock *sk; +}; +static LIST_HEAD(uevent_sock_list); +static DEFINE_MUTEX(uevent_sock_mutex); #endif -#ifdef CONFIG_KOBJECT_UEVENT -static struct sock *uevent_sock; +/* the strings here must match the enum in include/linux/kobject.h */ +static const char *kobject_actions[] = { + [KOBJ_ADD] = "add", + [KOBJ_REMOVE] = "remove", + [KOBJ_CHANGE] = "change", + [KOBJ_MOVE] = "move", + [KOBJ_ONLINE] = "online", + [KOBJ_OFFLINE] = "offline", +}; /** - * send_uevent - notify userspace by sending event trough netlink socket + * kobject_action_type - translate action string to numeric type + * + * @buf: buffer containing the action string, newline is ignored + * @len: length of buffer + * @type: pointer to the location to store the action type * - * @signal: signal name - * @obj: object path (kobject) - * @envp: possible hotplug environment to pass with the message - * @gfp_mask: + * Returns 0 if the action string was recognized. */ -static int send_uevent(const char *signal, const char *obj, - char **envp, int gfp_mask) +int kobject_action_type(const char *buf, size_t count, + enum kobject_action *type) { - struct sk_buff *skb; - char *pos; - int len; - - if (!uevent_sock) - return -EIO; - - len = strlen(signal) + 1; - len += strlen(obj) + 1; - - /* allocate buffer with the maximum possible message size */ - skb = alloc_skb(len + BUFFER_SIZE, gfp_mask); - if (!skb) - return -ENOMEM; - - pos = skb_put(skb, len); - sprintf(pos, "%s@%s", signal, obj); - - /* copy the environment key by key to our continuous buffer */ - if (envp) { - int i; - - for (i = 2; envp[i]; i++) { - len = strlen(envp[i]) + 1; - pos = skb_put(skb, len); - strcpy(pos, envp[i]); - } + enum kobject_action action; + int ret = -EINVAL; + + if (count && (buf[count-1] == '\n' || buf[count-1] == '\0')) + count--; + + if (!count) + goto out; + + for (action = 0; action < ARRAY_SIZE(kobject_actions); action++) { + if (strncmp(kobject_actions[action], buf, count) != 0) + continue; + if (kobject_actions[action][count] != '\0') + continue; + *type = action; + ret = 0; + break; } - - return netlink_broadcast(uevent_sock, skb, 0, 1, gfp_mask); +out: + return ret; } -static int do_kobject_uevent(struct kobject *kobj, enum kobject_action action, - struct attribute *attr, int gfp_mask) +static int kobj_bcast_filter(struct sock *dsk, struct sk_buff *skb, void *data) { - char *path; - char *attrpath; - char *signal; - int len; - int rc = -ENOMEM; - - path = kobject_get_path(kobj, gfp_mask); - if (!path) - return -ENOMEM; - - signal = action_to_string(action); - if (!signal) - return -EINVAL; - - if (attr) { - len = strlen(path); - len += strlen(attr->name) + 2; - attrpath = kmalloc(len, gfp_mask); - if (!attrpath) - goto exit; - sprintf(attrpath, "%s/%s", path, attr->name); - rc = send_uevent(signal, attrpath, NULL, gfp_mask); - kfree(attrpath); - } else - rc = send_uevent(signal, path, NULL, gfp_mask); - -exit: - kfree(path); - return rc; -} - -/** - * kobject_uevent - notify userspace by sending event through netlink socket - * - * @signal: signal name - * @kobj: struct kobject that the event is happening to - * @attr: optional struct attribute the event belongs to - */ -int kobject_uevent(struct kobject *kobj, enum kobject_action action, - struct attribute *attr) -{ - return do_kobject_uevent(kobj, action, attr, GFP_KERNEL); -} -EXPORT_SYMBOL_GPL(kobject_uevent); - -int kobject_uevent_atomic(struct kobject *kobj, enum kobject_action action, - struct attribute *attr) -{ - return do_kobject_uevent(kobj, action, attr, GFP_ATOMIC); -} -EXPORT_SYMBOL_GPL(kobject_uevent_atomic); - -static int __init kobject_uevent_init(void) -{ - uevent_sock = netlink_kernel_create(NETLINK_KOBJECT_UEVENT, NULL); - - if (!uevent_sock) { - printk(KERN_ERR - "kobject_uevent: unable to create netlink socket!\n"); - return -ENODEV; + struct kobject *kobj = data; + const struct kobj_ns_type_operations *ops; + + ops = kobj_ns_ops(kobj); + if (ops) { + const void *sock_ns, *ns; + ns = kobj->ktype->namespace(kobj); + sock_ns = ops->netlink_ns(dsk); + return sock_ns != ns; } return 0; } -postcore_initcall(kobject_uevent_init); - -#else -static inline int send_uevent(const char *signal, const char *obj, - char **envp, int gfp_mask) +static int kobj_usermode_filter(struct kobject *kobj) { + const struct kobj_ns_type_operations *ops; + + ops = kobj_ns_ops(kobj); + if (ops) { + const void *init_ns, *ns; + ns = kobj->ktype->namespace(kobj); + init_ns = ops->initial_ns(); + return ns != init_ns; + } + return 0; } -#endif /* CONFIG_KOBJECT_UEVENT */ - - -#ifdef CONFIG_HOTPLUG -char hotplug_path[HOTPLUG_PATH_LEN] = "/sbin/hotplug"; -u64 hotplug_seqnum; -static DEFINE_SPINLOCK(sequence_lock); - /** - * kobject_hotplug - notify userspace by executing /sbin/hotplug + * kobject_uevent_env - send an uevent with environmental data * - * @action: action that is happening (usually "ADD" or "REMOVE") + * @action: action that is happening * @kobj: struct kobject that the action is happening to + * @envp_ext: pointer to environmental data + * + * Returns 0 if kobject_uevent() is completed with success or the + * corresponding error when it fails. */ -void kobject_hotplug(struct kobject *kobj, enum kobject_action action) +int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, + char *envp_ext[]) { - char *argv [3]; - char **envp = NULL; - char *buffer = NULL; - char *seq_buff; - char *scratch; - int i = 0; - int retval; - char *kobj_path = NULL; - char *name = NULL; - char *action_string; - u64 seq; - struct kobject *top_kobj = kobj; + struct kobj_uevent_env *env; + const char *action_string = kobject_actions[action]; + const char *devpath = NULL; + const char *subsystem; + struct kobject *top_kobj; struct kset *kset; - static struct kset_hotplug_ops null_hotplug_ops; - struct kset_hotplug_ops *hotplug_ops = &null_hotplug_ops; - - /* If this kobj does not belong to a kset, - try to find a parent that does. */ - if (!top_kobj->kset && top_kobj->parent) { - do { - top_kobj = top_kobj->parent; - } while (!top_kobj->kset && top_kobj->parent); - } + const struct kset_uevent_ops *uevent_ops; + u64 seq; + int i = 0; + int retval = 0; +#ifdef CONFIG_NET + struct uevent_sock *ue_sk; +#endif - if (top_kobj->kset) - kset = top_kobj->kset; - else - return; + pr_debug("kobject: '%s' (%p): %s\n", + kobject_name(kobj), kobj, __func__); - if (kset->hotplug_ops) - hotplug_ops = kset->hotplug_ops; + /* search the kset we belong to */ + top_kobj = kobj; + while (!top_kobj->kset && top_kobj->parent) + top_kobj = top_kobj->parent; - /* If the kset has a filter operation, call it. - Skip the event, if the filter returns zero. */ - if (hotplug_ops->filter) { - if (!hotplug_ops->filter(kset, kobj)) - return; + if (!top_kobj->kset) { + pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " + "without kset!\n", kobject_name(kobj), kobj, + __func__); + return -EINVAL; } - pr_debug ("%s\n", __FUNCTION__); - - action_string = action_to_string(action); - if (!action_string) - return; - - envp = kmalloc(NUM_ENVP * sizeof (char *), GFP_KERNEL); - if (!envp) - return; - memset (envp, 0x00, NUM_ENVP * sizeof (char *)); - - buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL); - if (!buffer) - goto exit; - - if (hotplug_ops->name) - name = hotplug_ops->name(kset, kobj); - if (name == NULL) - name = kobject_name(&kset->kobj); + kset = top_kobj->kset; + uevent_ops = kset->uevent_ops; - argv [0] = hotplug_path; - argv [1] = name; - argv [2] = NULL; - - /* minimal command environment */ - envp [i++] = "HOME=/"; - envp [i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + /* skip the event, if uevent_suppress is set*/ + if (kobj->uevent_suppress) { + pr_debug("kobject: '%s' (%p): %s: uevent_suppress " + "caused the event to drop!\n", + kobject_name(kobj), kobj, __func__); + return 0; + } + /* skip the event, if the filter returns zero. */ + if (uevent_ops && uevent_ops->filter) + if (!uevent_ops->filter(kset, kobj)) { + pr_debug("kobject: '%s' (%p): %s: filter function " + "caused the event to drop!\n", + kobject_name(kobj), kobj, __func__); + return 0; + } - scratch = buffer; + /* originating subsystem */ + if (uevent_ops && uevent_ops->name) + subsystem = uevent_ops->name(kset, kobj); + else + subsystem = kobject_name(&kset->kobj); + if (!subsystem) { + pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the " + "event to drop!\n", kobject_name(kobj), kobj, + __func__); + return 0; + } - envp [i++] = scratch; - scratch += sprintf(scratch, "ACTION=%s", action_string) + 1; + /* environment buffer */ + env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); + if (!env) + return -ENOMEM; - kobj_path = kobject_get_path(kobj, GFP_KERNEL); - if (!kobj_path) + /* complete object path */ + devpath = kobject_get_path(kobj, GFP_KERNEL); + if (!devpath) { + retval = -ENOENT; goto exit; + } - envp [i++] = scratch; - scratch += sprintf (scratch, "DEVPATH=%s", kobj_path) + 1; - - envp [i++] = scratch; - scratch += sprintf(scratch, "SUBSYSTEM=%s", name) + 1; + /* default keys */ + retval = add_uevent_var(env, "ACTION=%s", action_string); + if (retval) + goto exit; + retval = add_uevent_var(env, "DEVPATH=%s", devpath); + if (retval) + goto exit; + retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); + if (retval) + goto exit; - /* reserve space for the sequence, - * put the real one in after the hotplug call */ - envp[i++] = seq_buff = scratch; - scratch += strlen("SEQNUM=18446744073709551616") + 1; + /* keys passed in from the caller */ + if (envp_ext) { + for (i = 0; envp_ext[i]; i++) { + retval = add_uevent_var(env, "%s", envp_ext[i]); + if (retval) + goto exit; + } + } - if (hotplug_ops->hotplug) { - /* have the kset specific function add its stuff */ - retval = hotplug_ops->hotplug (kset, kobj, - &envp[i], NUM_ENVP - i, scratch, - BUFFER_SIZE - (scratch - buffer)); + /* let the kset specific function add its stuff */ + if (uevent_ops && uevent_ops->uevent) { + retval = uevent_ops->uevent(kset, kobj, env); if (retval) { - pr_debug ("%s - hotplug() returned %d\n", - __FUNCTION__, retval); + pr_debug("kobject: '%s' (%p): %s: uevent() returned " + "%d\n", kobject_name(kobj), kobj, + __func__, retval); goto exit; } } + /* + * Mark "add" and "remove" events in the object to ensure proper + * events to userspace during automatic cleanup. If the object did + * send an "add" event, "remove" will automatically generated by + * the core, if not already done by the caller. + */ + if (action == KOBJ_ADD) + kobj->state_add_uevent_sent = 1; + else if (action == KOBJ_REMOVE) + kobj->state_remove_uevent_sent = 1; + + /* we will send an event, so request a new sequence number */ spin_lock(&sequence_lock); - seq = ++hotplug_seqnum; + seq = ++uevent_seqnum; spin_unlock(&sequence_lock); - sprintf(seq_buff, "SEQNUM=%llu", (unsigned long long)seq); + retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq); + if (retval) + goto exit; - pr_debug ("%s: %s %s seq=%llu %s %s %s %s %s\n", - __FUNCTION__, argv[0], argv[1], (unsigned long long)seq, - envp[0], envp[1], envp[2], envp[3], envp[4]); +#if defined(CONFIG_NET) + /* send netlink message */ + mutex_lock(&uevent_sock_mutex); + list_for_each_entry(ue_sk, &uevent_sock_list, list) { + struct sock *uevent_sock = ue_sk->sk; + struct sk_buff *skb; + size_t len; + + /* allocate message with the maximum possible size */ + len = strlen(action_string) + strlen(devpath) + 2; + skb = alloc_skb(len + env->buflen, GFP_KERNEL); + if (skb) { + char *scratch; + + /* add header */ + scratch = skb_put(skb, len); + sprintf(scratch, "%s@%s", action_string, devpath); + + /* copy keys to our continuous event payload buffer */ + for (i = 0; i < env->envp_idx; i++) { + len = strlen(env->envp[i]) + 1; + scratch = skb_put(skb, len); + strcpy(scratch, env->envp[i]); + } + + NETLINK_CB(skb).dst_group = 1; + retval = netlink_broadcast_filtered(uevent_sock, skb, + 0, 1, GFP_KERNEL, + kobj_bcast_filter, + kobj); + /* ENOBUFS should be handled in userspace */ + if (retval == -ENOBUFS) + retval = 0; + } else + retval = -ENOMEM; + } + mutex_unlock(&uevent_sock_mutex); +#endif - send_uevent(action_string, kobj_path, envp, GFP_KERNEL); + /* call uevent_helper, usually only enabled during early boot */ + if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { + char *argv [3]; - if (!hotplug_path[0]) - goto exit; + argv [0] = uevent_helper; + argv [1] = (char *)subsystem; + argv [2] = NULL; + retval = add_uevent_var(env, "HOME=/"); + if (retval) + goto exit; + retval = add_uevent_var(env, + "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); + if (retval) + goto exit; - retval = call_usermodehelper (argv[0], argv, envp, 0); - if (retval) - pr_debug ("%s - call_usermodehelper returned %d\n", - __FUNCTION__, retval); + retval = call_usermodehelper(argv[0], argv, + env->envp, UMH_WAIT_EXEC); + } exit: - kfree(kobj_path); - kfree(buffer); - kfree(envp); - return; + kfree(devpath); + kfree(env); + return retval; } -EXPORT_SYMBOL(kobject_hotplug); +EXPORT_SYMBOL_GPL(kobject_uevent_env); /** - * add_hotplug_env_var - helper for creating hotplug environment variables - * @envp: Pointer to table of environment variables, as passed into - * hotplug() method. - * @num_envp: Number of environment variable slots available, as - * passed into hotplug() method. - * @cur_index: Pointer to current index into @envp. It should be - * initialized to 0 before the first call to add_hotplug_env_var(), - * and will be incremented on success. - * @buffer: Pointer to buffer for environment variables, as passed - * into hotplug() method. - * @buffer_size: Length of @buffer, as passed into hotplug() method. - * @cur_len: Pointer to current length of space used in @buffer. - * Should be initialized to 0 before the first call to - * add_hotplug_env_var(), and will be incremented on success. - * @format: Format for creating environment variable (of the form - * "XXX=%x") for snprintf(). + * kobject_uevent - notify userspace by ending an uevent + * + * @action: action that is happening + * @kobj: struct kobject that the action is happening to + * + * Returns 0 if kobject_uevent() is completed with success or the + * corresponding error when it fails. + */ +int kobject_uevent(struct kobject *kobj, enum kobject_action action) +{ + return kobject_uevent_env(kobj, action, NULL); +} +EXPORT_SYMBOL_GPL(kobject_uevent); + +/** + * add_uevent_var - add key value string to the environment buffer + * @env: environment buffer structure + * @format: printf format for the key=value pair * * Returns 0 if environment variable was added successfully or -ENOMEM * if no space was available. */ -int add_hotplug_env_var(char **envp, int num_envp, int *cur_index, - char *buffer, int buffer_size, int *cur_len, - const char *format, ...) +int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...) { va_list args; + int len; - /* - * We check against num_envp - 1 to make sure there is at - * least one slot left after we return, since the hotplug - * method needs to set the last slot to NULL. - */ - if (*cur_index >= num_envp - 1) + if (env->envp_idx >= ARRAY_SIZE(env->envp)) { + WARN(1, KERN_ERR "add_uevent_var: too many keys\n"); return -ENOMEM; - - envp[*cur_index] = buffer + *cur_len; + } va_start(args, format); - *cur_len += vsnprintf(envp[*cur_index], - max(buffer_size - *cur_len, 0), - format, args) + 1; + len = vsnprintf(&env->buf[env->buflen], + sizeof(env->buf) - env->buflen, + format, args); va_end(args); - if (*cur_len > buffer_size) + if (len >= (sizeof(env->buf) - env->buflen)) { + WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n"); return -ENOMEM; + } - (*cur_index)++; + env->envp[env->envp_idx++] = &env->buf[env->buflen]; + env->buflen += len + 1; + return 0; +} +EXPORT_SYMBOL_GPL(add_uevent_var); + +#if defined(CONFIG_NET) +static int uevent_net_init(struct net *net) +{ + struct uevent_sock *ue_sk; + + ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL); + if (!ue_sk) + return -ENOMEM; + + ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, + 1, NULL, NULL, THIS_MODULE); + if (!ue_sk->sk) { + printk(KERN_ERR + "kobject_uevent: unable to create netlink socket!\n"); + return -ENODEV; + } + mutex_lock(&uevent_sock_mutex); + list_add_tail(&ue_sk->list, &uevent_sock_list); + mutex_unlock(&uevent_sock_mutex); return 0; } -EXPORT_SYMBOL(add_hotplug_env_var); -#endif /* CONFIG_HOTPLUG */ +static void uevent_net_exit(struct net *net) +{ + struct uevent_sock *ue_sk; + + mutex_lock(&uevent_sock_mutex); + list_for_each_entry(ue_sk, &uevent_sock_list, list) { + if (sock_net(ue_sk->sk) == net) + goto found; + } + mutex_unlock(&uevent_sock_mutex); + return; + +found: + list_del(&ue_sk->list); + mutex_unlock(&uevent_sock_mutex); + + netlink_kernel_release(ue_sk->sk); + kfree(ue_sk); +} + +static struct pernet_operations uevent_net_ops = { + .init = uevent_net_init, + .exit = uevent_net_exit, +}; + +static int __init kobject_uevent_init(void) +{ + netlink_set_nonroot(NETLINK_KOBJECT_UEVENT, NL_NONROOT_RECV); + return register_pernet_subsys(&uevent_net_ops); +} + + +postcore_initcall(kobject_uevent_init); +#endif