2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* kernel userspace event delivery
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004 Red Hat, Inc. All rights reserved.
|
|
|
|
* Copyright (C) 2004 Novell, Inc. All rights reserved.
|
|
|
|
* Copyright (C) 2004 IBM, Inc. All rights reserved.
|
|
|
|
*
|
|
|
|
* Licensed under the GNU GPL v2.
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Robert Love <rml@novell.com>
|
|
|
|
* Kay Sievers <kay.sievers@vrfy.org>
|
|
|
|
* Arjan van de Ven <arjanv@redhat.com>
|
|
|
|
* Greg Kroah-Hartman <greg@kroah.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/spinlock.h>
|
2008-03-28 05:26:30 +08:00
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/kobject.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/socket.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/netlink.h>
|
|
|
|
#include <net/sock.h>
|
|
|
|
|
|
|
|
|
2007-07-20 19:58:13 +08:00
|
|
|
u64 uevent_seqnum;
|
2007-08-15 21:38:28 +08:00
|
|
|
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
|
2007-07-20 19:58:13 +08:00
|
|
|
static DEFINE_SPINLOCK(sequence_lock);
|
|
|
|
#if defined(CONFIG_NET)
|
|
|
|
static struct sock *uevent_sock;
|
|
|
|
#endif
|
|
|
|
|
2007-08-13 02:43:55 +08:00
|
|
|
/* 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",
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* Returns 0 if the action string was recognized.
|
|
|
|
*/
|
|
|
|
int kobject_action_type(const char *buf, size_t count,
|
|
|
|
enum kobject_action *type)
|
|
|
|
{
|
|
|
|
enum kobject_action action;
|
|
|
|
int ret = -EINVAL;
|
|
|
|
|
2008-03-29 07:05:25 +08:00
|
|
|
if (count && (buf[count-1] == '\n' || buf[count-1] == '\0'))
|
2007-08-13 02:43:55 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/**
|
2006-11-21 00:07:51 +08:00
|
|
|
* kobject_uevent_env - send an uevent with environmental data
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
2007-08-13 02:43:55 +08:00
|
|
|
* @action: action that is happening
|
2005-04-17 06:20:36 +08:00
|
|
|
* @kobj: struct kobject that the action is happening to
|
2006-11-21 00:07:51 +08:00
|
|
|
* @envp_ext: pointer to environmental data
|
2006-12-20 05:01:27 +08:00
|
|
|
*
|
|
|
|
* Returns 0 if kobject_uevent() is completed with success or the
|
|
|
|
* corresponding error when it fails.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2006-12-20 05:01:27 +08:00
|
|
|
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
|
2007-08-14 21:15:12 +08:00
|
|
|
char *envp_ext[])
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-08-14 21:15:12 +08:00
|
|
|
struct kobj_uevent_env *env;
|
|
|
|
const char *action_string = kobject_actions[action];
|
2005-11-11 21:43:07 +08:00
|
|
|
const char *devpath = NULL;
|
|
|
|
const char *subsystem;
|
|
|
|
struct kobject *top_kobj;
|
|
|
|
struct kset *kset;
|
2005-11-16 16:00:00 +08:00
|
|
|
struct kset_uevent_ops *uevent_ops;
|
2005-11-11 21:43:07 +08:00
|
|
|
u64 seq;
|
2005-04-17 06:20:36 +08:00
|
|
|
int i = 0;
|
2006-12-20 05:01:27 +08:00
|
|
|
int retval = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-11-29 15:49:41 +08:00
|
|
|
pr_debug("kobject: '%s' (%p): %s\n",
|
2008-04-30 15:55:08 +08:00
|
|
|
kobject_name(kobj), kobj, __func__);
|
2005-11-11 21:43:07 +08:00
|
|
|
|
|
|
|
/* search the kset we belong to */
|
|
|
|
top_kobj = kobj;
|
2007-08-13 02:43:55 +08:00
|
|
|
while (!top_kobj->kset && top_kobj->parent)
|
2007-04-04 19:39:17 +08:00
|
|
|
top_kobj = top_kobj->parent;
|
2007-08-13 02:43:55 +08:00
|
|
|
|
2006-12-20 05:01:27 +08:00
|
|
|
if (!top_kobj->kset) {
|
2007-11-29 15:49:41 +08:00
|
|
|
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
|
|
|
|
"without kset!\n", kobject_name(kobj), kobj,
|
2008-04-30 15:55:08 +08:00
|
|
|
__func__);
|
2006-12-20 05:01:27 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-11 21:43:07 +08:00
|
|
|
kset = top_kobj->kset;
|
2005-11-16 16:00:00 +08:00
|
|
|
uevent_ops = kset->uevent_ops;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-03-01 21:10:49 +08:00
|
|
|
/* 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;
|
|
|
|
}
|
2007-08-14 21:15:12 +08:00
|
|
|
/* skip the event, if the filter returns zero. */
|
2005-11-16 16:00:00 +08:00
|
|
|
if (uevent_ops && uevent_ops->filter)
|
2006-12-20 05:01:27 +08:00
|
|
|
if (!uevent_ops->filter(kset, kobj)) {
|
2007-11-29 15:49:41 +08:00
|
|
|
pr_debug("kobject: '%s' (%p): %s: filter function "
|
|
|
|
"caused the event to drop!\n",
|
2008-04-30 15:55:08 +08:00
|
|
|
kobject_name(kobj), kobj, __func__);
|
2006-12-20 05:01:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-14 10:25:56 +08:00
|
|
|
/* originating subsystem */
|
|
|
|
if (uevent_ops && uevent_ops->name)
|
|
|
|
subsystem = uevent_ops->name(kset, kobj);
|
|
|
|
else
|
|
|
|
subsystem = kobject_name(&kset->kobj);
|
|
|
|
if (!subsystem) {
|
2007-11-29 15:49:41 +08:00
|
|
|
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
|
|
|
|
"event to drop!\n", kobject_name(kobj), kobj,
|
2008-04-30 15:55:08 +08:00
|
|
|
__func__);
|
2007-03-14 10:25:56 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-08-14 21:15:12 +08:00
|
|
|
/* environment buffer */
|
|
|
|
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
|
|
|
|
if (!env)
|
2006-12-20 05:01:27 +08:00
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-11 21:43:07 +08:00
|
|
|
/* complete object path */
|
|
|
|
devpath = kobject_get_path(kobj, GFP_KERNEL);
|
2006-12-20 05:01:27 +08:00
|
|
|
if (!devpath) {
|
|
|
|
retval = -ENOENT;
|
2005-11-11 21:43:07 +08:00
|
|
|
goto exit;
|
2006-12-20 05:01:27 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-11 21:43:07 +08:00
|
|
|
/* default keys */
|
2007-08-14 21:15:12 +08:00
|
|
|
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;
|
|
|
|
|
|
|
|
/* keys passed in from the caller */
|
|
|
|
if (envp_ext) {
|
|
|
|
for (i = 0; envp_ext[i]; i++) {
|
2008-11-13 12:20:00 +08:00
|
|
|
retval = add_uevent_var(env, "%s", envp_ext[i]);
|
2007-08-14 21:15:12 +08:00
|
|
|
if (retval)
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-11 21:43:07 +08:00
|
|
|
/* let the kset specific function add its stuff */
|
2005-11-16 16:00:00 +08:00
|
|
|
if (uevent_ops && uevent_ops->uevent) {
|
2007-08-14 21:15:12 +08:00
|
|
|
retval = uevent_ops->uevent(kset, kobj, env);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (retval) {
|
2007-11-29 15:49:41 +08:00
|
|
|
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
|
|
|
|
"%d\n", kobject_name(kobj), kobj,
|
2008-04-30 15:55:08 +08:00
|
|
|
__func__, retval);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-12-19 08:40:42 +08:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2007-08-14 21:15:12 +08:00
|
|
|
/* we will send an event, so request a new sequence number */
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_lock(&sequence_lock);
|
2005-11-16 16:00:00 +08:00
|
|
|
seq = ++uevent_seqnum;
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock(&sequence_lock);
|
2007-08-14 21:15:12 +08:00
|
|
|
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
|
|
|
|
if (retval)
|
|
|
|
goto exit;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-04-25 21:37:26 +08:00
|
|
|
#if defined(CONFIG_NET)
|
2005-11-11 21:43:07 +08:00
|
|
|
/* send netlink message */
|
|
|
|
if (uevent_sock) {
|
|
|
|
struct sk_buff *skb;
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
/* allocate message with the maximum possible size */
|
|
|
|
len = strlen(action_string) + strlen(devpath) + 2;
|
2007-08-14 21:15:12 +08:00
|
|
|
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
|
2005-11-11 21:43:07 +08:00
|
|
|
if (skb) {
|
2007-08-14 21:15:12 +08:00
|
|
|
char *scratch;
|
|
|
|
|
2005-11-11 21:43:07 +08:00
|
|
|
/* add header */
|
|
|
|
scratch = skb_put(skb, len);
|
|
|
|
sprintf(scratch, "%s@%s", action_string, devpath);
|
|
|
|
|
|
|
|
/* copy keys to our continuous event payload buffer */
|
2007-08-14 21:15:12 +08:00
|
|
|
for (i = 0; i < env->envp_idx; i++) {
|
|
|
|
len = strlen(env->envp[i]) + 1;
|
2005-11-11 21:43:07 +08:00
|
|
|
scratch = skb_put(skb, len);
|
2007-08-14 21:15:12 +08:00
|
|
|
strcpy(scratch, env->envp[i]);
|
2005-11-11 21:43:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
NETLINK_CB(skb).dst_group = 1;
|
2008-11-16 18:23:27 +08:00
|
|
|
retval = netlink_broadcast(uevent_sock, skb, 0, 1,
|
|
|
|
GFP_KERNEL);
|
netlink: change return-value logic of netlink_broadcast()
Currently, netlink_broadcast() reports errors to the caller if no
messages at all were delivered:
1) If, at least, one message has been delivered correctly, returns 0.
2) Otherwise, if no messages at all were delivered due to skb_clone()
failure, return -ENOBUFS.
3) Otherwise, if there are no listeners, return -ESRCH.
With this patch, the caller knows if the delivery of any of the
messages to the listeners have failed:
1) If it fails to deliver any message (for whatever reason), return
-ENOBUFS.
2) Otherwise, if all messages were delivered OK, returns 0.
3) Otherwise, if no listeners, return -ESRCH.
In the current ctnetlink code and in Netfilter in general, we can add
reliable logging and connection tracking event delivery by dropping the
packets whose events were not successfully delivered over Netlink. Of
course, this option would be settable via /proc as this approach reduces
performance (in terms of filtered connections per seconds by a stateful
firewall) but providing reliable logging and event delivery (for
conntrackd) in return.
This patch also changes some clients of netlink_broadcast() that
may report ENOBUFS errors via printk. This error handling is not
of any help. Instead, the userspace daemons that are listening to
those netlink messages should resync themselves with the kernel-side
if they hit ENOBUFS.
BTW, netlink_broadcast() clients include those that call
cn_netlink_send(), nlmsg_multicast() and genlmsg_multicast() since they
internally call netlink_broadcast() and return its error value.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2009-02-06 15:56:36 +08:00
|
|
|
/* ENOBUFS should be handled in userspace */
|
|
|
|
if (retval == -ENOBUFS)
|
|
|
|
retval = 0;
|
2008-11-16 18:23:27 +08:00
|
|
|
} else
|
|
|
|
retval = -ENOMEM;
|
2005-11-11 21:43:07 +08:00
|
|
|
}
|
2006-04-25 21:37:26 +08:00
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-11 21:43:07 +08:00
|
|
|
/* call uevent_helper, usually only enabled during early boot */
|
2005-11-16 16:00:00 +08:00
|
|
|
if (uevent_helper[0]) {
|
2005-11-11 21:43:07 +08:00
|
|
|
char *argv [3];
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-16 16:00:00 +08:00
|
|
|
argv [0] = uevent_helper;
|
2005-11-11 21:43:07 +08:00
|
|
|
argv [1] = (char *)subsystem;
|
|
|
|
argv [2] = NULL;
|
2007-08-14 21:15:12 +08:00
|
|
|
retval = add_uevent_var(env, "HOME=/");
|
|
|
|
if (retval)
|
|
|
|
goto exit;
|
2008-01-25 13:59:04 +08:00
|
|
|
retval = add_uevent_var(env,
|
|
|
|
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
|
2007-08-14 21:15:12 +08:00
|
|
|
if (retval)
|
|
|
|
goto exit;
|
|
|
|
|
2008-06-24 16:59:02 +08:00
|
|
|
retval = call_usermodehelper(argv[0], argv,
|
kobject: don't block for each kobject_uevent
Right now, the kobject_uevent code blocks for each uevent that's being
generated, due to using (for hystoric reasons) UHM_WAIT_EXEC as flag to
call_usermode_helper(). Specifically, the effect is that each uevent
that is being sent causes the code to wake up keventd, then block until
keventd has processed the work. Needless to say, this happens many times
during the system boot.
This patches changes that to UHN_NO_WAIT (brilliant name for a constant
btw) so that we only schedule the work to fire the uevent message, but
do not wait for keventd to process the work.
This removes one of the bottlenecks during boot; each one of them is
only a small effect, but the sum of them does add up.
[Note, distros that need this are broken, they should be setting
CONFIG_UEVENT_HELPER_PATH to "", that way this code path will never be
excuted at all -- gregkh]
Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2009-03-20 00:09:05 +08:00
|
|
|
env->envp, UMH_NO_WAIT);
|
2005-11-11 21:43:07 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
exit:
|
2005-11-11 21:43:07 +08:00
|
|
|
kfree(devpath);
|
2007-08-14 21:15:12 +08:00
|
|
|
kfree(env);
|
2006-12-20 05:01:27 +08:00
|
|
|
return retval;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-11-21 00:07:51 +08:00
|
|
|
EXPORT_SYMBOL_GPL(kobject_uevent_env);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* kobject_uevent - notify userspace by ending an uevent
|
|
|
|
*
|
2007-08-13 02:43:55 +08:00
|
|
|
* @action: action that is happening
|
2006-11-21 00:07:51 +08:00
|
|
|
* @kobj: struct kobject that the action is happening to
|
2006-12-20 05:01:27 +08:00
|
|
|
*
|
|
|
|
* Returns 0 if kobject_uevent() is completed with success or the
|
|
|
|
* corresponding error when it fails.
|
2006-11-21 00:07:51 +08:00
|
|
|
*/
|
2006-12-20 05:01:27 +08:00
|
|
|
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
|
2006-11-21 00:07:51 +08:00
|
|
|
{
|
2006-12-20 05:01:27 +08:00
|
|
|
return kobject_uevent_env(kobj, action, NULL);
|
2006-11-21 00:07:51 +08:00
|
|
|
}
|
2005-11-16 16:00:00 +08:00
|
|
|
EXPORT_SYMBOL_GPL(kobject_uevent);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/**
|
2007-08-14 21:15:12 +08:00
|
|
|
* add_uevent_var - add key value string to the environment buffer
|
|
|
|
* @env: environment buffer structure
|
|
|
|
* @format: printf format for the key=value pair
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
* Returns 0 if environment variable was added successfully or -ENOMEM
|
|
|
|
* if no space was available.
|
|
|
|
*/
|
2007-08-14 21:15:12 +08:00
|
|
|
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
va_list args;
|
2007-08-14 21:15:12 +08:00
|
|
|
int len;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-08-14 21:15:12 +08:00
|
|
|
if (env->envp_idx >= ARRAY_SIZE(env->envp)) {
|
2008-07-26 10:45:39 +08:00
|
|
|
WARN(1, KERN_ERR "add_uevent_var: too many keys\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENOMEM;
|
2007-08-14 21:15:12 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
va_start(args, format);
|
2007-08-14 21:15:12 +08:00
|
|
|
len = vsnprintf(&env->buf[env->buflen],
|
|
|
|
sizeof(env->buf) - env->buflen,
|
|
|
|
format, args);
|
2005-04-17 06:20:36 +08:00
|
|
|
va_end(args);
|
|
|
|
|
2007-08-14 21:15:12 +08:00
|
|
|
if (len >= (sizeof(env->buf) - env->buflen)) {
|
2008-07-26 10:45:39 +08:00
|
|
|
WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
return -ENOMEM;
|
2007-08-14 21:15:12 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-08-14 21:15:12 +08:00
|
|
|
env->envp[env->envp_idx++] = &env->buf[env->buflen];
|
|
|
|
env->buflen += len + 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2005-11-16 16:00:00 +08:00
|
|
|
EXPORT_SYMBOL_GPL(add_uevent_var);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-04-25 21:37:26 +08:00
|
|
|
#if defined(CONFIG_NET)
|
2005-11-11 21:43:07 +08:00
|
|
|
static int __init kobject_uevent_init(void)
|
|
|
|
{
|
2007-09-12 19:05:38 +08:00
|
|
|
uevent_sock = netlink_kernel_create(&init_net, NETLINK_KOBJECT_UEVENT,
|
|
|
|
1, NULL, NULL, THIS_MODULE);
|
2005-11-11 21:43:07 +08:00
|
|
|
if (!uevent_sock) {
|
|
|
|
printk(KERN_ERR
|
|
|
|
"kobject_uevent: unable to create netlink socket!\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
postcore_initcall(kobject_uevent_init);
|
2006-04-25 21:37:26 +08:00
|
|
|
#endif
|