2005-04-17 02:20:36 +04: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>
# include <linux/socket.h>
# include <linux/skbuff.h>
# include <linux/netlink.h>
# include <linux/string.h>
# include <linux/kobject.h>
# include <net/sock.h>
2007-07-20 15:58:13 +04:00
u64 uevent_seqnum ;
2007-08-15 17:38:28 +04:00
char uevent_helper [ UEVENT_HELPER_PATH_LEN ] = CONFIG_UEVENT_HELPER_PATH ;
2007-07-20 15:58:13 +04:00
static DEFINE_SPINLOCK ( sequence_lock ) ;
# if defined(CONFIG_NET)
static struct sock * uevent_sock ;
# endif
2007-08-12 22:43:55 +04: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 ;
if ( count & & buf [ count - 1 ] = = ' \n ' )
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 02:20:36 +04:00
/**
2006-11-20 19:07:51 +03:00
* kobject_uevent_env - send an uevent with environmental data
2005-04-17 02:20:36 +04:00
*
2007-08-12 22:43:55 +04:00
* @ action : action that is happening
2005-04-17 02:20:36 +04:00
* @ kobj : struct kobject that the action is happening to
2006-11-20 19:07:51 +03:00
* @ envp_ext : pointer to environmental data
2006-12-20 00:01:27 +03:00
*
* Returns 0 if kobject_uevent ( ) is completed with success or the
* corresponding error when it fails .
2005-04-17 02:20:36 +04:00
*/
2006-12-20 00:01:27 +03:00
int kobject_uevent_env ( struct kobject * kobj , enum kobject_action action ,
2007-08-14 17:15:12 +04:00
char * envp_ext [ ] )
2005-04-17 02:20:36 +04:00
{
2007-08-14 17:15:12 +04:00
struct kobj_uevent_env * env ;
const char * action_string = kobject_actions [ action ] ;
2005-11-11 16:43:07 +03:00
const char * devpath = NULL ;
const char * subsystem ;
struct kobject * top_kobj ;
struct kset * kset ;
2005-11-16 11:00:00 +03:00
struct kset_uevent_ops * uevent_ops ;
2005-11-11 16:43:07 +03:00
u64 seq ;
2005-04-17 02:20:36 +04:00
int i = 0 ;
2006-12-20 00:01:27 +03:00
int retval = 0 ;
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
pr_debug ( " %s \n " , __FUNCTION__ ) ;
/* search the kset we belong to */
top_kobj = kobj ;
2007-08-12 22:43:55 +04:00
while ( ! top_kobj - > kset & & top_kobj - > parent )
2007-04-04 15:39:17 +04:00
top_kobj = top_kobj - > parent ;
2007-08-12 22:43:55 +04:00
2006-12-20 00:01:27 +03:00
if ( ! top_kobj - > kset ) {
pr_debug ( " kobject attempted to send uevent without kset! \n " ) ;
return - EINVAL ;
}
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
kset = top_kobj - > kset ;
2005-11-16 11:00:00 +03:00
uevent_ops = kset - > uevent_ops ;
2005-04-17 02:20:36 +04:00
2007-08-14 17:15:12 +04:00
/* skip the event, if the filter returns zero. */
2005-11-16 11:00:00 +03:00
if ( uevent_ops & & uevent_ops - > filter )
2006-12-20 00:01:27 +03:00
if ( ! uevent_ops - > filter ( kset , kobj ) ) {
pr_debug ( " kobject filter function caused the event to drop! \n " ) ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
2007-03-14 05:25:56 +03:00
/* originating subsystem */
if ( uevent_ops & & uevent_ops - > name )
subsystem = uevent_ops - > name ( kset , kobj ) ;
else
subsystem = kobject_name ( & kset - > kobj ) ;
if ( ! subsystem ) {
pr_debug ( " unset subsytem caused the event to drop! \n " ) ;
return 0 ;
}
2007-08-14 17:15:12 +04:00
/* environment buffer */
env = kzalloc ( sizeof ( struct kobj_uevent_env ) , GFP_KERNEL ) ;
if ( ! env )
2006-12-20 00:01:27 +03:00
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
/* complete object path */
devpath = kobject_get_path ( kobj , GFP_KERNEL ) ;
2006-12-20 00:01:27 +03:00
if ( ! devpath ) {
retval = - ENOENT ;
2005-11-11 16:43:07 +03:00
goto exit ;
2006-12-20 00:01:27 +03:00
}
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
/* default keys */
2007-08-14 17:15:12 +04: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 + + ) {
retval = add_uevent_var ( env , envp_ext [ i ] ) ;
if ( retval )
goto exit ;
}
}
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
/* let the kset specific function add its stuff */
2005-11-16 11:00:00 +03:00
if ( uevent_ops & & uevent_ops - > uevent ) {
2007-08-14 17:15:12 +04:00
retval = uevent_ops - > uevent ( kset , kobj , env ) ;
2005-04-17 02:20:36 +04:00
if ( retval ) {
2005-11-16 11:00:00 +03:00
pr_debug ( " %s - uevent() returned %d \n " ,
2005-04-17 02:20:36 +04:00
__FUNCTION__ , retval ) ;
goto exit ;
}
}
2007-08-14 17:15:12 +04:00
/* we will send an event, so request a new sequence number */
2005-04-17 02:20:36 +04:00
spin_lock ( & sequence_lock ) ;
2005-11-16 11:00:00 +03:00
seq = + + uevent_seqnum ;
2005-04-17 02:20:36 +04:00
spin_unlock ( & sequence_lock ) ;
2007-08-14 17:15:12 +04:00
retval = add_uevent_var ( env , " SEQNUM=%llu " , ( unsigned long long ) seq ) ;
if ( retval )
goto exit ;
2005-04-17 02:20:36 +04:00
2006-04-25 17:37:26 +04:00
# if defined(CONFIG_NET)
2005-11-11 16:43:07 +03: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 17:15:12 +04:00
skb = alloc_skb ( len + env - > buflen , GFP_KERNEL ) ;
2005-11-11 16:43:07 +03:00
if ( skb ) {
2007-08-14 17:15:12 +04:00
char * scratch ;
2005-11-11 16:43:07 +03: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 17:15:12 +04:00
for ( i = 0 ; i < env - > envp_idx ; i + + ) {
len = strlen ( env - > envp [ i ] ) + 1 ;
2005-11-11 16:43:07 +03:00
scratch = skb_put ( skb , len ) ;
2007-08-14 17:15:12 +04:00
strcpy ( scratch , env - > envp [ i ] ) ;
2005-11-11 16:43:07 +03:00
}
NETLINK_CB ( skb ) . dst_group = 1 ;
netlink_broadcast ( uevent_sock , skb , 0 , 1 , GFP_KERNEL ) ;
}
}
2006-04-25 17:37:26 +04:00
# endif
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
/* call uevent_helper, usually only enabled during early boot */
2005-11-16 11:00:00 +03:00
if ( uevent_helper [ 0 ] ) {
2005-11-11 16:43:07 +03:00
char * argv [ 3 ] ;
2005-04-17 02:20:36 +04:00
2005-11-16 11:00:00 +03:00
argv [ 0 ] = uevent_helper ;
2005-11-11 16:43:07 +03:00
argv [ 1 ] = ( char * ) subsystem ;
argv [ 2 ] = NULL ;
2007-08-14 17:15:12 +04:00
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 ;
call_usermodehelper ( argv [ 0 ] , argv , env - > envp , UMH_WAIT_EXEC ) ;
2005-11-11 16:43:07 +03:00
}
2005-04-17 02:20:36 +04:00
exit :
2005-11-11 16:43:07 +03:00
kfree ( devpath ) ;
2007-08-14 17:15:12 +04:00
kfree ( env ) ;
2006-12-20 00:01:27 +03:00
return retval ;
2005-04-17 02:20:36 +04:00
}
2006-11-20 19:07:51 +03:00
EXPORT_SYMBOL_GPL ( kobject_uevent_env ) ;
/**
* kobject_uevent - notify userspace by ending an uevent
*
2007-08-12 22:43:55 +04:00
* @ action : action that is happening
2006-11-20 19:07:51 +03:00
* @ kobj : struct kobject that the action is happening to
2006-12-20 00:01:27 +03:00
*
* Returns 0 if kobject_uevent ( ) is completed with success or the
* corresponding error when it fails .
2006-11-20 19:07:51 +03:00
*/
2006-12-20 00:01:27 +03:00
int kobject_uevent ( struct kobject * kobj , enum kobject_action action )
2006-11-20 19:07:51 +03:00
{
2006-12-20 00:01:27 +03:00
return kobject_uevent_env ( kobj , action , NULL ) ;
2006-11-20 19:07:51 +03:00
}
2005-11-16 11:00:00 +03:00
EXPORT_SYMBOL_GPL ( kobject_uevent ) ;
2005-04-17 02:20:36 +04:00
/**
2007-08-14 17:15:12 +04: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 02:20:36 +04:00
*
* Returns 0 if environment variable was added successfully or - ENOMEM
* if no space was available .
*/
2007-08-14 17:15:12 +04:00
int add_uevent_var ( struct kobj_uevent_env * env , const char * format , . . . )
2005-04-17 02:20:36 +04:00
{
va_list args ;
2007-08-14 17:15:12 +04:00
int len ;
2005-04-17 02:20:36 +04:00
2007-08-14 17:15:12 +04:00
if ( env - > envp_idx > = ARRAY_SIZE ( env - > envp ) ) {
printk ( KERN_ERR " add_uevent_var: too many keys \n " ) ;
WARN_ON ( 1 ) ;
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2007-08-14 17:15:12 +04:00
}
2005-04-17 02:20:36 +04:00
va_start ( args , format ) ;
2007-08-14 17:15:12 +04:00
len = vsnprintf ( & env - > buf [ env - > buflen ] ,
sizeof ( env - > buf ) - env - > buflen ,
format , args ) ;
2005-04-17 02:20:36 +04:00
va_end ( args ) ;
2007-08-14 17:15:12 +04:00
if ( len > = ( sizeof ( env - > buf ) - env - > buflen ) ) {
printk ( KERN_ERR " add_uevent_var: buffer size too small \n " ) ;
WARN_ON ( 1 ) ;
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2007-08-14 17:15:12 +04:00
}
2005-04-17 02:20:36 +04:00
2007-08-14 17:15:12 +04:00
env - > envp [ env - > envp_idx + + ] = & env - > buf [ env - > buflen ] ;
env - > buflen + = len + 1 ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-11-16 11:00:00 +03:00
EXPORT_SYMBOL_GPL ( add_uevent_var ) ;
2005-04-17 02:20:36 +04:00
2006-04-25 17:37:26 +04:00
# if defined(CONFIG_NET)
2005-11-11 16:43:07 +03:00
static int __init kobject_uevent_init ( void )
{
2007-09-12 15:05:38 +04:00
uevent_sock = netlink_kernel_create ( & init_net , NETLINK_KOBJECT_UEVENT ,
1 , NULL , NULL , THIS_MODULE ) ;
2005-11-11 16:43:07 +03: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 17:37:26 +04:00
# endif