2007-07-04 11:45:46 +04:00
/*
event script handling
Copyright ( C ) Andrew Tridgell 2007
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
2007-07-10 09:29:31 +04:00
the Free Software Foundation ; either version 3 of the License , or
2007-07-04 11:45:46 +04:00
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
2007-07-10 09:29:31 +04:00
along with this program ; if not , see < http : //www.gnu.org/licenses/>.
2007-07-04 11:45:46 +04:00
*/
# include "includes.h"
# include "system/filesys.h"
# include "system/wait.h"
2007-08-20 05:10:30 +04:00
# include "system/dir.h"
# include "system/locale.h"
2007-07-04 11:45:46 +04:00
# include "../include/ctdb_private.h"
# include "lib/events/events.h"
2007-08-15 08:44:03 +04:00
# include "../common/rb_tree.h"
2007-08-20 05:10:30 +04:00
static struct {
struct timeval start ;
const char * script_running ;
} child_state ;
/*
ctdbd sends us a SIGTERM when we should time out the current script
*/
static void sigterm ( int sig )
{
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_ERR , ( " Timed out running script '%s' after %.1f seconds \n " ,
2007-08-20 05:10:30 +04:00
child_state . script_running , timeval_elapsed ( & child_state . start ) ) ) ;
/* all the child processes will be running in the same process group */
kill ( - getpgrp ( ) , SIGKILL ) ;
exit ( 1 ) ;
}
2007-07-04 11:45:46 +04:00
2008-06-13 06:18:00 +04:00
struct ctdb_event_script_state {
struct ctdb_context * ctdb ;
pid_t child ;
void ( * callback ) ( struct ctdb_context * , int , void * ) ;
int fd [ 2 ] ;
void * private_data ;
const char * options ;
} ;
2007-07-04 11:45:46 +04:00
/*
run the event script - varargs version
2007-08-15 04:48:10 +04:00
this function is called and run in the context of a forked child
which allows it to do blocking calls such as system ( )
2007-07-04 11:45:46 +04:00
*/
2008-06-13 06:18:00 +04:00
static int ctdb_event_script_v ( struct ctdb_context * ctdb , const char * options )
2007-07-04 11:45:46 +04:00
{
2008-06-13 06:18:00 +04:00
char * cmdstr ;
2007-07-04 11:45:46 +04:00
int ret ;
struct stat st ;
2007-08-15 08:44:03 +04:00
TALLOC_CTX * tmp_ctx = talloc_new ( ctdb ) ;
trbt_tree_t * tree ;
DIR * dir ;
struct dirent * de ;
char * script ;
2007-07-04 11:45:46 +04:00
2008-05-14 14:57:04 +04:00
if ( ctdb - > recovery_mode ! = CTDB_RECOVERY_NORMAL ) {
/* we guarantee that only some specifically allowed event scripts are run
while in recovery */
const char * allowed_scripts [ ] = { " startrecovery " , " shutdown " } ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( allowed_scripts ) ; i + + ) {
if ( strcmp ( options , allowed_scripts [ i ] ) = = 0 ) break ;
}
if ( i = = ARRAY_SIZE ( allowed_scripts ) ) {
DEBUG ( 0 , ( " Refusing to run event scripts with option '%s' while in recovery \n " ,
options ) ) ;
2008-05-14 16:05:09 +04:00
return - 1 ;
2008-05-14 14:57:04 +04:00
}
}
2007-08-20 05:10:30 +04:00
if ( setpgid ( 0 , 0 ) ! = 0 ) {
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_ERR , ( " Failed to create process group for event scripts - %s \n " ,
2007-08-20 05:10:30 +04:00
strerror ( errno ) ) ) ;
talloc_free ( tmp_ctx ) ;
return - 1 ;
}
signal ( SIGTERM , sigterm ) ;
child_state . start = timeval_current ( ) ;
child_state . script_running = " startup " ;
2007-08-15 08:44:03 +04:00
/*
the service specific event scripts
*/
2007-09-04 03:50:07 +04:00
if ( stat ( ctdb - > event_script_dir , & st ) ! = 0 & &
2007-07-04 11:45:46 +04:00
errno = = ENOENT ) {
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_CRIT , ( " No event script directory found at '%s' \n " , ctdb - > event_script_dir ) ) ;
2007-08-15 08:44:03 +04:00
talloc_free ( tmp_ctx ) ;
2007-08-20 05:10:30 +04:00
return - 1 ;
2007-08-15 08:44:03 +04:00
}
/* create a tree to store all the script names in */
tree = trbt_create ( tmp_ctx , 0 ) ;
/* scan all directory entries and insert all valid scripts into the
tree
*/
2007-09-04 03:50:07 +04:00
dir = opendir ( ctdb - > event_script_dir ) ;
2007-08-15 08:44:03 +04:00
if ( dir = = NULL ) {
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_CRIT , ( " Failed to open event script directory '%s' \n " , ctdb - > event_script_dir ) ) ;
2007-08-15 08:44:03 +04:00
talloc_free ( tmp_ctx ) ;
2007-08-20 05:10:30 +04:00
return - 1 ;
2007-07-04 11:45:46 +04:00
}
2007-08-15 08:44:03 +04:00
while ( ( de = readdir ( dir ) ) ! = NULL ) {
int namlen ;
2007-08-20 05:10:30 +04:00
unsigned num ;
2007-08-21 03:22:14 +04:00
char * str ;
2007-08-15 08:44:03 +04:00
namlen = strlen ( de - > d_name ) ;
2007-07-04 11:45:46 +04:00
2007-08-15 08:44:03 +04:00
if ( namlen < 3 ) {
continue ;
}
2007-07-04 11:45:46 +04:00
2007-08-15 08:44:03 +04:00
if ( de - > d_name [ namlen - 1 ] = = ' ~ ' ) {
/* skip files emacs left behind */
continue ;
}
2007-07-04 11:45:46 +04:00
2007-08-15 08:44:03 +04:00
if ( de - > d_name [ 2 ] ! = ' . ' ) {
continue ;
}
2007-08-20 05:10:30 +04:00
if ( sscanf ( de - > d_name , " %02u. " , & num ) ! = 1 ) {
2007-08-15 08:44:03 +04:00
continue ;
}
2007-08-21 03:22:14 +04:00
/* Make sure the event script is executable */
2007-09-04 03:50:07 +04:00
str = talloc_asprintf ( tree , " %s/%s " , ctdb - > event_script_dir , de - > d_name ) ;
2007-08-21 03:22:14 +04:00
if ( stat ( str , & st ) ! = 0 ) {
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_ERR , ( " Could not stat event script %s. Ignoring this event script \n " , str ) ) ;
2007-08-21 03:22:14 +04:00
continue ;
}
if ( ! ( st . st_mode & S_IXUSR ) ) {
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_ERR , ( " Event script %s is not executable. Ignoring this event script \n " , str ) ) ;
2007-08-21 03:22:14 +04:00
continue ;
}
2007-08-15 08:44:03 +04:00
/* store the event script in the tree */
2007-08-20 05:10:30 +04:00
script = trbt_insert32 ( tree , num , talloc_strdup ( tree , de - > d_name ) ) ;
2007-08-15 08:44:03 +04:00
if ( script ! = NULL ) {
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_CRIT , ( " CONFIG ERROR: Multiple event scripts with the same prefix : '%s' and '%s'. Each event script MUST have a unique prefix \n " , script , de - > d_name ) ) ;
2007-08-15 08:44:03 +04:00
talloc_free ( tmp_ctx ) ;
closedir ( dir ) ;
return - 1 ;
}
2007-07-04 11:45:46 +04:00
}
2007-08-15 08:44:03 +04:00
closedir ( dir ) ;
/* fetch the scripts from the tree one by one and execute
them
*/
while ( ( script = trbt_findfirstarray32 ( tree , 1 ) ) ! = NULL ) {
cmdstr = talloc_asprintf ( tmp_ctx , " %s/%s %s " ,
2007-09-04 03:50:07 +04:00
ctdb - > event_script_dir ,
2007-08-15 08:44:03 +04:00
script , options ) ;
CTDB_NO_MEMORY ( ctdb , cmdstr ) ;
2007-07-04 11:45:46 +04:00
2008-02-04 09:44:24 +03:00
DEBUG ( DEBUG_INFO , ( " Executing event script %s \n " , cmdstr ) ) ;
2007-07-04 11:45:46 +04:00
2007-08-20 05:10:30 +04:00
child_state . start = timeval_current ( ) ;
child_state . script_running = cmdstr ;
2007-08-15 08:44:03 +04:00
ret = system ( cmdstr ) ;
/* if the system() call was successful, translate ret into the
return code from the command
*/
if ( ret ! = - 1 ) {
ret = WEXITSTATUS ( ret ) ;
}
/* return an error if the script failed */
if ( ret ! = 0 ) {
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_ERR , ( " Event script %s failed with error %d \n " , cmdstr , ret ) ) ;
2007-08-15 08:44:03 +04:00
talloc_free ( tmp_ctx ) ;
return ret ;
}
2007-08-15 08:46:06 +04:00
/* remove this script from the tree */
2007-08-15 08:44:03 +04:00
talloc_free ( script ) ;
}
2007-08-20 05:10:30 +04:00
child_state . start = timeval_current ( ) ;
child_state . script_running = " finished " ;
2007-08-15 08:44:03 +04:00
talloc_free ( tmp_ctx ) ;
return 0 ;
2007-07-04 11:45:46 +04:00
}
/* called when child is finished */
static void ctdb_event_script_handler ( struct event_context * ev , struct fd_event * fde ,
uint16_t flags , void * p )
{
struct ctdb_event_script_state * state =
talloc_get_type ( p , struct ctdb_event_script_state ) ;
void ( * callback ) ( struct ctdb_context * , int , void * ) = state - > callback ;
void * private_data = state - > private_data ;
struct ctdb_context * ctdb = state - > ctdb ;
2008-07-09 08:02:54 +04:00
signed char rt = - 1 ;
read ( state - > fd [ 0 ] , & rt , sizeof ( rt ) ) ;
2007-07-04 11:45:46 +04:00
talloc_set_destructor ( state , NULL ) ;
talloc_free ( state ) ;
2008-07-09 08:02:54 +04:00
callback ( ctdb , rt , private_data ) ;
2008-06-13 07:18:06 +04:00
ctdb - > event_script_timeouts = 0 ;
}
static void ctdb_ban_self ( struct ctdb_context * ctdb , uint32_t ban_period )
{
int ret ;
struct ctdb_ban_info b ;
TDB_DATA data ;
b . pnn = ctdb - > pnn ;
b . ban_time = ban_period ;
data . dptr = ( uint8_t * ) & b ;
data . dsize = sizeof ( b ) ;
ret = ctdb_daemon_send_message ( ctdb , CTDB_BROADCAST_CONNECTED ,
CTDB_SRVID_BAN_NODE , data ) ;
if ( ret ! = 0 ) {
DEBUG ( DEBUG_ERR , ( __location__ " Failed to send ban message \n " ) ) ;
}
2007-07-04 11:45:46 +04:00
}
/* called when child times out */
static void ctdb_event_script_timeout ( struct event_context * ev , struct timed_event * te ,
struct timeval t , void * p )
{
struct ctdb_event_script_state * state = talloc_get_type ( p , struct ctdb_event_script_state ) ;
void ( * callback ) ( struct ctdb_context * , int , void * ) = state - > callback ;
void * private_data = state - > private_data ;
struct ctdb_context * ctdb = state - > ctdb ;
2008-06-13 07:18:06 +04:00
DEBUG ( DEBUG_ERR , ( " Event script timed out : %s count : %u \n " , state - > options , ctdb - > event_script_timeouts ) ) ;
2008-07-07 14:38:59 +04:00
if ( ! strcmp ( state - > options , " monitor " ) ) {
/* if it is a monitor event, we allow it to "hang" a few times
before we declare it a failure and ban ourself ( and make
ourself unhealthy )
*/
DEBUG ( DEBUG_ERR , ( __location__ " eventscript for monitor event timedout. \n " ) ) ;
ctdb - > event_script_timeouts + + ;
if ( ctdb - > event_script_timeouts > ctdb - > tunable . script_ban_count ) {
ctdb - > event_script_timeouts = 0 ;
DEBUG ( DEBUG_ERR , ( " Maximum timeout count %u reached for eventscript. Banning self for %d seconds \n " , ctdb - > tunable . script_ban_count , ctdb - > tunable . recovery_ban_period ) ) ;
ctdb_ban_self ( ctdb , ctdb - > tunable . recovery_ban_period ) ;
callback ( ctdb , - 1 , private_data ) ;
} else {
callback ( ctdb , 0 , private_data ) ;
}
} else if ( ! strcmp ( state - > options , " startup " ) ) {
DEBUG ( DEBUG_ERR , ( __location__ " eventscript for startup event timedout. \n " ) ) ;
callback ( ctdb , - 1 , private_data ) ;
} else {
/* if it is not a monitor event we ban ourself immediately */
DEBUG ( DEBUG_ERR , ( __location__ " eventscript for NON-monitor/NON-startup event timedout. Immediately banning ourself for %d seconds \n " , ctdb - > tunable . recovery_ban_period ) ) ;
2008-06-13 07:18:06 +04:00
ctdb_ban_self ( ctdb , ctdb - > tunable . recovery_ban_period ) ;
2008-07-07 14:38:59 +04:00
callback ( ctdb , - 1 , private_data ) ;
2008-06-13 07:18:06 +04:00
}
2008-07-07 14:38:59 +04:00
talloc_free ( state ) ;
2007-07-04 11:45:46 +04:00
}
/*
destroy a running event script
*/
static int event_script_destructor ( struct ctdb_event_script_state * state )
{
2008-02-04 12:07:15 +03:00
DEBUG ( DEBUG_ERR , ( __location__ " Sending SIGTERM to child pid:%d \n " , state - > child ) ) ;
2007-08-20 05:10:30 +04:00
kill ( state - > child , SIGTERM ) ;
2007-07-04 11:45:46 +04:00
return 0 ;
}
/*
run the event script in the background , calling the callback when
finished
*/
2007-07-19 07:36:00 +04:00
static int ctdb_event_script_callback_v ( struct ctdb_context * ctdb ,
struct timeval timeout ,
TALLOC_CTX * mem_ctx ,
void ( * callback ) ( struct ctdb_context * , int , void * ) ,
void * private_data ,
const char * fmt , va_list ap )
2007-07-04 11:45:46 +04:00
{
struct ctdb_event_script_state * state ;
int ret ;
state = talloc ( mem_ctx , struct ctdb_event_script_state ) ;
CTDB_NO_MEMORY ( ctdb , state ) ;
state - > ctdb = ctdb ;
state - > callback = callback ;
state - > private_data = private_data ;
2008-06-13 06:18:00 +04:00
state - > options = talloc_vasprintf ( state , fmt , ap ) ;
CTDB_NO_MEMORY ( ctdb , state - > options ) ;
2007-07-04 11:45:46 +04:00
ret = pipe ( state - > fd ) ;
if ( ret ! = 0 ) {
talloc_free ( state ) ;
return - 1 ;
}
state - > child = fork ( ) ;
if ( state - > child = = ( pid_t ) - 1 ) {
close ( state - > fd [ 0 ] ) ;
close ( state - > fd [ 1 ] ) ;
talloc_free ( state ) ;
return - 1 ;
}
if ( state - > child = = 0 ) {
2008-07-09 08:02:54 +04:00
signed char rt ;
2007-07-04 11:45:46 +04:00
close ( state - > fd [ 0 ] ) ;
2007-07-13 02:47:02 +04:00
if ( ctdb - > do_setsched ) {
2007-07-13 03:35:46 +04:00
ctdb_restore_scheduler ( ctdb ) ;
2007-07-13 02:47:02 +04:00
}
2007-07-04 11:45:46 +04:00
set_close_on_exec ( state - > fd [ 1 ] ) ;
2008-07-09 08:02:54 +04:00
rt = ctdb_event_script_v ( ctdb , state - > options ) ;
while ( ( ret = write ( state - > fd [ 1 ] , & rt , sizeof ( rt ) ) ) ! = sizeof ( rt ) ) {
sleep ( 1 ) ;
}
_exit ( rt ) ;
2007-07-04 11:45:46 +04:00
}
talloc_set_destructor ( state , event_script_destructor ) ;
close ( state - > fd [ 1 ] ) ;
event_add_fd ( ctdb - > ev , state , state - > fd [ 0 ] , EVENT_FD_READ | EVENT_FD_AUTOCLOSE ,
ctdb_event_script_handler , state ) ;
if ( ! timeval_is_zero ( & timeout ) ) {
event_add_timed ( ctdb - > ev , state , timeout , ctdb_event_script_timeout , state ) ;
2008-06-13 06:18:00 +04:00
} else {
2008-06-13 07:18:06 +04:00
DEBUG ( DEBUG_ERR , ( __location__ " eventscript %s called with no timeout \n " , state - > options ) ) ;
2007-07-04 11:45:46 +04:00
}
return 0 ;
}
2007-07-19 07:36:00 +04:00
/*
run the event script in the background , calling the callback when
finished
*/
int ctdb_event_script_callback ( struct ctdb_context * ctdb ,
struct timeval timeout ,
TALLOC_CTX * mem_ctx ,
void ( * callback ) ( struct ctdb_context * , int , void * ) ,
void * private_data ,
const char * fmt , . . . )
{
va_list ap ;
int ret ;
va_start ( ap , fmt ) ;
ret = ctdb_event_script_callback_v ( ctdb , timeout , mem_ctx , callback , private_data , fmt , ap ) ;
va_end ( ap ) ;
return ret ;
}
struct callback_status {
bool done ;
int status ;
} ;
/*
called when ctdb_event_script ( ) finishes
*/
static void event_script_callback ( struct ctdb_context * ctdb , int status , void * private_data )
{
struct callback_status * s = ( struct callback_status * ) private_data ;
s - > done = true ;
s - > status = status ;
}
/*
run the event script , waiting for it to complete . Used when the caller doesn ' t want to
continue till the event script has finished .
*/
int ctdb_event_script ( struct ctdb_context * ctdb , const char * fmt , . . . )
{
va_list ap ;
int ret ;
TALLOC_CTX * tmp_ctx = talloc_new ( ctdb ) ;
struct callback_status status ;
va_start ( ap , fmt ) ;
ret = ctdb_event_script_callback_v ( ctdb , timeval_zero ( ) , tmp_ctx , event_script_callback , & status , fmt , ap ) ;
va_end ( ap ) ;
if ( ret ! = 0 ) {
talloc_free ( tmp_ctx ) ;
return ret ;
}
status . status = - 1 ;
status . done = false ;
while ( status . done = = false & & event_loop_once ( ctdb - > ev ) = = 0 ) /* noop */ ;
talloc_free ( tmp_ctx ) ;
return status . status ;
}
2008-04-02 04:13:30 +04:00
struct eventscript_callback_state {
struct ctdb_req_control * c ;
} ;
/*
called when takeip event finishes
*/
static void run_eventscripts_callback ( struct ctdb_context * ctdb , int status ,
void * private_data )
{
struct eventscript_callback_state * state =
talloc_get_type ( private_data , struct eventscript_callback_state ) ;
ctdb_enable_monitoring ( ctdb ) ;
if ( status ! = 0 ) {
DEBUG ( DEBUG_ERR , ( __location__ " Failed to forcibly run eventscripts \n " ) ) ;
ctdb_request_control_reply ( ctdb , state - > c , NULL , status , NULL ) ;
talloc_free ( state ) ;
return ;
}
/* the control succeeded */
ctdb_request_control_reply ( ctdb , state - > c , NULL , 0 , NULL ) ;
talloc_free ( state ) ;
return ;
}
/*
A control to force running of the eventscripts from the ctdb client tool
*/
int32_t ctdb_run_eventscripts ( struct ctdb_context * ctdb ,
struct ctdb_req_control * c ,
TDB_DATA indata , bool * async_reply )
{
int ret ;
struct eventscript_callback_state * state ;
/* kill off any previous invokations of forced eventscripts */
if ( ctdb - > eventscripts_ctx ) {
talloc_free ( ctdb - > eventscripts_ctx ) ;
}
ctdb - > eventscripts_ctx = talloc_new ( ctdb ) ;
CTDB_NO_MEMORY ( ctdb , ctdb - > eventscripts_ctx ) ;
state = talloc ( ctdb - > eventscripts_ctx , struct eventscript_callback_state ) ;
CTDB_NO_MEMORY ( ctdb , state ) ;
state - > c = talloc_steal ( ctdb , c ) ;
DEBUG ( DEBUG_NOTICE , ( " Forced running of eventscripts with arguments %s \n " , indata . dptr ) ) ;
ctdb_disable_monitoring ( ctdb ) ;
ret = ctdb_event_script_callback ( ctdb ,
timeval_current_ofs ( ctdb - > tunable . script_timeout , 0 ) ,
state , run_eventscripts_callback , state ,
( const char * ) indata . dptr ) ;
if ( ret ! = 0 ) {
ctdb_enable_monitoring ( ctdb ) ;
DEBUG ( DEBUG_ERR , ( __location__ " Failed to run eventscripts with arguments %s \n " , indata . dptr ) ) ;
talloc_free ( state ) ;
return - 1 ;
}
/* tell ctdb_control.c that we will be replying asynchronously */
* async_reply = true ;
return 0 ;
}