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>
2006-01-25 02:21:32 +03:00
# define BUFFER_SIZE 2048 /* buffer for the variables */
2005-04-17 02:20:36 +04:00
# define NUM_ENVP 32 /* number of env pointers */
2007-07-09 00:29:26 +04:00
/* the strings here must match the enum in include/linux/kobject.h */
const char * kobject_actions [ ] = {
" add " ,
" remove " ,
" change " ,
" move " ,
" online " ,
" offline " ,
} ;
2005-04-17 02:20:36 +04:00
2007-07-20 15:58:13 +04:00
# if defined(CONFIG_HOTPLUG)
u64 uevent_seqnum ;
char uevent_helper [ UEVENT_HELPER_PATH_LEN ] = " /sbin/hotplug " ;
static DEFINE_SPINLOCK ( sequence_lock ) ;
# if defined(CONFIG_NET)
static struct sock * uevent_sock ;
# endif
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
*
2006-11-20 19:07:51 +03:00
* @ action : action that is happening ( usually KOBJ_MOVE )
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 ,
2006-11-20 19:07:51 +03:00
char * envp_ext [ ] )
2005-04-17 02:20:36 +04:00
{
2005-11-11 16:43:07 +03:00
char * * envp ;
char * buffer ;
2005-04-17 02:20:36 +04:00
char * scratch ;
2005-11-11 16:43:07 +03:00
const char * action_string ;
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 ;
char * seq_buff ;
2005-04-17 02:20:36 +04:00
int i = 0 ;
2006-12-20 00:01:27 +03:00
int retval = 0 ;
2006-11-20 19:07:51 +03:00
int j ;
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
pr_debug ( " %s \n " , __FUNCTION__ ) ;
2007-07-09 00:29:26 +04:00
action_string = kobject_actions [ action ] ;
2006-12-20 00:01:27 +03:00
if ( ! action_string ) {
pr_debug ( " kobject attempted to send uevent without action_string! \n " ) ;
return - EINVAL ;
}
2005-11-11 16:43:07 +03:00
/* search the kset we belong to */
top_kobj = kobj ;
2007-04-04 15:39:17 +04:00
while ( ! top_kobj - > kset & & top_kobj - > parent ) {
top_kobj = top_kobj - > parent ;
2005-04-17 02:20:36 +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
2005-11-11 16:43:07 +03: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 ;
}
2005-11-11 16:43:07 +03:00
/* environment index */
envp = kzalloc ( NUM_ENVP * sizeof ( char * ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! envp )
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
/* environment values */
2005-04-17 02:20:36 +04:00
buffer = kmalloc ( BUFFER_SIZE , GFP_KERNEL ) ;
2006-12-20 00:01:27 +03:00
if ( ! buffer ) {
retval = - ENOMEM ;
2005-04-17 02:20:36 +04: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
/* 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
/* event environemnt for helper process only */
envp [ i + + ] = " HOME=/ " ;
envp [ i + + ] = " PATH=/sbin:/bin:/usr/sbin:/usr/bin " ;
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
/* default keys */
2005-04-17 02:20:36 +04:00
scratch = buffer ;
envp [ i + + ] = scratch ;
scratch + = sprintf ( scratch , " ACTION=%s " , action_string ) + 1 ;
envp [ i + + ] = scratch ;
2005-11-11 16:43:07 +03:00
scratch + = sprintf ( scratch , " DEVPATH=%s " , devpath ) + 1 ;
2005-04-17 02:20:36 +04:00
envp [ i + + ] = scratch ;
2005-11-11 16:43:07 +03:00
scratch + = sprintf ( scratch , " SUBSYSTEM=%s " , subsystem ) + 1 ;
2006-11-20 19:07:51 +03:00
for ( j = 0 ; envp_ext & & envp_ext [ j ] ; j + + )
envp [ i + + ] = envp_ext [ j ] ;
2005-11-11 16:43:07 +03:00
/* just reserve the space, overwrite it after kset call has returned */
2005-04-17 02:20:36 +04:00
envp [ i + + ] = seq_buff = scratch ;
scratch + = strlen ( " SEQNUM=18446744073709551616 " ) + 1 ;
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 ) {
retval = uevent_ops - > uevent ( kset , kobj ,
2005-04-17 02:20:36 +04:00
& envp [ i ] , NUM_ENVP - i , scratch ,
BUFFER_SIZE - ( scratch - buffer ) ) ;
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 ;
}
}
2005-11-11 16:43:07 +03:00
/* we will send an event, 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 ) ;
sprintf ( seq_buff , " SEQNUM=%llu " , ( unsigned long long ) seq ) ;
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 ;
skb = alloc_skb ( len + BUFFER_SIZE , GFP_KERNEL ) ;
if ( skb ) {
/* add header */
scratch = skb_put ( skb , len ) ;
sprintf ( scratch , " %s@%s " , action_string , devpath ) ;
/* copy keys to our continuous event payload buffer */
for ( i = 2 ; envp [ i ] ; i + + ) {
len = strlen ( envp [ i ] ) + 1 ;
scratch = skb_put ( skb , len ) ;
strcpy ( scratch , envp [ i ] ) ;
}
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-07-18 05:37:03 +04:00
call_usermodehelper ( argv [ 0 ] , argv , 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 ) ;
2005-04-17 02:20:36 +04:00
kfree ( buffer ) ;
kfree ( envp ) ;
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
*
* @ action : action that is happening ( usually KOBJ_ADD and KOBJ_REMOVE )
* @ 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
/**
2005-11-16 11:00:00 +03:00
* add_uevent_var - helper for creating event variables
2005-04-17 02:20:36 +04:00
* @ envp : Pointer to table of environment variables , as passed into
2005-11-16 11:00:00 +03:00
* uevent ( ) method .
2005-04-17 02:20:36 +04:00
* @ num_envp : Number of environment variable slots available , as
2005-11-16 11:00:00 +03:00
* passed into uevent ( ) method .
2005-04-17 02:20:36 +04:00
* @ cur_index : Pointer to current index into @ envp . It should be
2005-11-16 11:00:00 +03:00
* initialized to 0 before the first call to add_uevent_var ( ) ,
2005-04-17 02:20:36 +04:00
* and will be incremented on success .
* @ buffer : Pointer to buffer for environment variables , as passed
2005-11-16 11:00:00 +03:00
* into uevent ( ) method .
* @ buffer_size : Length of @ buffer , as passed into uevent ( ) method .
2005-04-17 02:20:36 +04:00
* @ cur_len : Pointer to current length of space used in @ buffer .
* Should be initialized to 0 before the first call to
2005-11-16 11:00:00 +03:00
* add_uevent_var ( ) , and will be incremented on success .
2005-04-17 02:20:36 +04:00
* @ format : Format for creating environment variable ( of the form
* " XXX=%x " ) for snprintf ( ) .
*
* Returns 0 if environment variable was added successfully or - ENOMEM
* if no space was available .
*/
2005-11-16 11:00:00 +03:00
int add_uevent_var ( char * * envp , int num_envp , int * cur_index ,
char * buffer , int buffer_size , int * cur_len ,
const char * format , . . . )
2005-04-17 02:20:36 +04:00
{
va_list args ;
/*
* We check against num_envp - 1 to make sure there is at
2005-11-16 11:00:00 +03:00
* least one slot left after we return , since kobject_uevent ( )
* needs to set the last slot to NULL .
2005-04-17 02:20:36 +04:00
*/
if ( * cur_index > = num_envp - 1 )
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 ;
va_end ( args ) ;
if ( * cur_len > buffer_size )
return - ENOMEM ;
( * cur_index ) + + ;
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 )
{
uevent_sock = netlink_kernel_create ( NETLINK_KOBJECT_UEVENT , 1 , NULL ,
2007-04-21 01:14:21 +04:00
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
2005-11-11 16:43:07 +03:00
2005-04-17 02:20:36 +04:00
# endif /* CONFIG_HOTPLUG */