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>
2005-11-16 11:00:00 +03:00
# define BUFFER_SIZE 1024 /* buffer for the variables */
2005-04-17 02:20:36 +04:00
# define NUM_ENVP 32 /* number of env pointers */
2005-11-23 10:36:13 +03:00
# if defined(CONFIG_HOTPLUG) && defined(CONFIG_NET)
2005-11-11 07:33:52 +03:00
static DEFINE_SPINLOCK ( sequence_lock ) ;
2005-11-11 16:43:07 +03:00
static struct sock * uevent_sock ;
2005-11-11 07:33:52 +03:00
2005-04-17 02:20:36 +04:00
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_OFFLINE :
return " offline " ;
case KOBJ_ONLINE :
return " online " ;
default :
return NULL ;
}
}
/**
2005-11-16 11:00:00 +03:00
* kobject_uevent - notify userspace by ending an uevent
2005-04-17 02:20:36 +04:00
*
2005-11-16 11:00:00 +03:00
* @ action : action that is happening ( usually KOBJ_ADD and KOBJ_REMOVE )
2005-04-17 02:20:36 +04:00
* @ kobj : struct kobject that the action is happening to
*/
2005-11-16 11:00:00 +03:00
void kobject_uevent ( struct kobject * kobj , enum kobject_action action )
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 ;
int retval ;
2005-11-11 16:43:07 +03:00
pr_debug ( " %s \n " , __FUNCTION__ ) ;
action_string = action_to_string ( action ) ;
if ( ! action_string )
return ;
/* search the kset we belong to */
top_kobj = kobj ;
2005-04-17 02:20:36 +04:00
if ( ! top_kobj - > kset & & top_kobj - > parent ) {
do {
top_kobj = top_kobj - > parent ;
} while ( ! top_kobj - > kset & & top_kobj - > parent ) ;
}
2005-11-11 16:43:07 +03:00
if ( ! top_kobj - > kset )
2005-04-17 02:20:36 +04:00
return ;
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 )
if ( ! uevent_ops - > filter ( kset , kobj ) )
2005-04-17 02:20:36 +04:00
return ;
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 )
return ;
2005-11-11 16:43:07 +03:00
/* environment values */
2005-04-17 02:20:36 +04:00
buffer = kmalloc ( BUFFER_SIZE , GFP_KERNEL ) ;
if ( ! buffer )
goto exit ;
2005-11-11 16:43:07 +03:00
/* complete object path */
devpath = kobject_get_path ( kobj , GFP_KERNEL ) ;
if ( ! devpath )
goto exit ;
2005-04-17 02:20:36 +04:00
2005-11-11 16:43:07 +03:00
/* originating subsystem */
2005-11-16 11:00:00 +03:00
if ( uevent_ops & & uevent_ops - > name )
subsystem = uevent_ops - > name ( kset , kobj ) ;
2005-11-11 16:43:07 +03:00
else
subsystem = kobject_name ( & kset - > kobj ) ;
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 ;
2005-04-17 02:20:36 +04:00
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 ) ;
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 ) ;
}
}
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 ;
call_usermodehelper ( argv [ 0 ] , argv , envp , 0 ) ;
}
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 ) ;
return ;
}
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
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 ,
THIS_MODULE ) ;
if ( ! uevent_sock ) {
printk ( KERN_ERR
" kobject_uevent: unable to create netlink socket! \n " ) ;
return - ENODEV ;
}
return 0 ;
}
postcore_initcall ( kobject_uevent_init ) ;
2005-04-17 02:20:36 +04:00
# endif /* CONFIG_HOTPLUG */