// // Copyright (c) 2014-2016 Wind River Systems, Inc. // // SPDX-License-Identifier: Apache-2.0 // #include "sm_service_group_notification.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "sm_types.h" #include "sm_utils.h" #include "sm_debug.h" #include "sm_process_death.h" #include "sm_service_domain_member_table.h" #include "sm_service_group_table.h" #include "sm_service_group_fsm.h" typedef struct { char seqnum_str[24]; bool part_of_aggregate; char service_group_aggregate_name[SM_SERVICE_GROUP_AGGREGATE_NAME_MAX_CHAR]; SmServiceGroupStateT service_group_aggregate_desired_state; SmServiceGroupStateT service_group_aggregate_state; char service_group_name[SM_SERVICE_GROUP_NAME_MAX_CHAR]; SmServiceGroupStateT service_group_desired_state; SmServiceGroupStateT service_group_state; SmServiceGroupNotificationT service_group_notification; } SmNotificationEnvT; #define SM_NOTIFICATION_SCRIPT_TIMEOUT_IN_MS 30000 #define SM_NOTIFICATION_SCRIPT_SUCCESS 0 #define SM_NOTIFICATION_SCRIPT_TIMEOUT -65534 #define SM_NOTIFICATION_SCRIPT_FAILURE -65535 static unsigned long _seqnum = 0; // **************************************************************************** // Service Group Notification - Do Abort // ===================================== static SmErrorT sm_service_group_notification_do_abort( char service_group_name[], int process_id ) { if( -1 == process_id ) { DPRINTFE( "Trying to abort a process (%i) for service group (%s) " "notificaton but pid is invalid.", process_id, service_group_name ); return( SM_FAILED ); } if( process_id == (int) getpid() ) { DPRINTFE( "Trying to abort a process (%i) for service group (%s) " "notification but pid is self.", process_id, service_group_name ); return( SM_FAILED ); } DPRINTFI( "Aborting service group (%s) notification with kill signal, " "pid=%i.", service_group_name, process_id ); if( 0 > kill( process_id, SIGKILL ) ) { if( ESRCH == errno ) { DPRINTFD( "Service group (%s) notification script not running.", service_group_name ); return( SM_OKAY ); } else { DPRINTFE( "Failed to send kill signal to service group (%s) " "notification script, error=%s.", service_group_name, strerror( errno ) ); return( SM_FAILED ); } } DPRINTFD( "Kill signal sent to service groupt (%s) notification " "script.", service_group_name ); return( SM_OKAY ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Service Group Aggregate // ==================================================== static void sm_service_group_notification_service_group_aggregate( void* user_data[], SmServiceDomainMemberT* member ) { SmServiceGroupStateT* aggregate_desired_state; SmServiceGroupStateT* aggregate_state; SmServiceGroupStateT* transition_state; SmServiceGroupT* service_group; aggregate_desired_state = (SmServiceGroupStateT*) user_data[0]; aggregate_state = (SmServiceGroupStateT*) user_data[1]; transition_state = (SmServiceGroupStateT*) user_data[2]; service_group = sm_service_group_table_read( member->service_group_name ); if( NULL == service_group ) { DPRINTFE( "Failed to read service group (%s), error=%s.", member->service_group_name, sm_error_str(SM_NOT_FOUND) ); return; } switch( service_group->desired_state ) { case SM_SERVICE_GROUP_STATE_ACTIVE: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *aggregate_desired_state )|| ( SM_SERVICE_GROUP_STATE_STANDBY != *aggregate_desired_state )|| ( SM_SERVICE_GROUP_STATE_DISABLED != *aggregate_desired_state )|| ( SM_SERVICE_GROUP_STATE_SHUTDOWN != *aggregate_desired_state )) *aggregate_desired_state = service_group->desired_state; break; case SM_SERVICE_GROUP_STATE_STANDBY: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *aggregate_desired_state )|| ( SM_SERVICE_GROUP_STATE_DISABLED != *aggregate_desired_state )|| ( SM_SERVICE_GROUP_STATE_SHUTDOWN != *aggregate_desired_state )) *aggregate_desired_state = service_group->desired_state; break; case SM_SERVICE_GROUP_STATE_DISABLED: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *aggregate_desired_state )|| ( SM_SERVICE_GROUP_STATE_SHUTDOWN != *aggregate_desired_state )) *aggregate_desired_state = service_group->desired_state; break; case SM_SERVICE_GROUP_STATE_SHUTDOWN: *aggregate_desired_state = service_group->desired_state; break; default: // Ignore break; } switch( service_group->state ) { case SM_SERVICE_GROUP_STATE_ACTIVE: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *aggregate_state )|| ( SM_SERVICE_GROUP_STATE_STANDBY != *aggregate_state )|| ( SM_SERVICE_GROUP_STATE_DISABLED != *aggregate_state )|| ( SM_SERVICE_GROUP_STATE_SHUTDOWN != *aggregate_state )) *aggregate_state = service_group->state; break; case SM_SERVICE_GROUP_STATE_STANDBY: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *aggregate_state )|| ( SM_SERVICE_GROUP_STATE_DISABLED != *aggregate_state )|| ( SM_SERVICE_GROUP_STATE_SHUTDOWN != *aggregate_state )) *aggregate_state = service_group->state; break; case SM_SERVICE_GROUP_STATE_DISABLED: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *aggregate_state )|| ( SM_SERVICE_GROUP_STATE_SHUTDOWN != *aggregate_state )) *aggregate_state = service_group->state; break; case SM_SERVICE_GROUP_STATE_SHUTDOWN: *aggregate_state = service_group->state; break; case SM_SERVICE_GROUP_STATE_GO_ACTIVE: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *transition_state )|| ( SM_SERVICE_GROUP_STATE_GO_STANDBY != *transition_state )|| ( SM_SERVICE_GROUP_STATE_DISABLING != *transition_state )) *transition_state = service_group->state; break; case SM_SERVICE_GROUP_STATE_GO_STANDBY: if(( SM_SERVICE_GROUP_STATE_UNKNOWN == *transition_state )|| ( SM_SERVICE_GROUP_STATE_DISABLING != *transition_state )) *transition_state = service_group->state; break; case SM_SERVICE_GROUP_STATE_DISABLING: *transition_state = service_group->state; break; default: // Ignore break; } } // **************************************************************************** // **************************************************************************** // Service Group Notification - Get Environment // ============================================ static SmErrorT sm_service_group_notification_get_env( char service_group_name[], SmServiceGroupNotificationT notification, SmNotificationEnvT* env ) { SmServiceGroupStateT aggregate_desired_state = SM_SERVICE_GROUP_STATE_UNKNOWN; SmServiceGroupStateT aggregate_state = SM_SERVICE_GROUP_STATE_UNKNOWN; SmServiceGroupStateT transition_state = SM_SERVICE_GROUP_STATE_UNKNOWN; void* user_data[] = {&aggregate_desired_state, &aggregate_state, &transition_state}; SmServiceDomainMemberT* service_domain_member = NULL; SmServiceGroupT* service_group = NULL; service_group = sm_service_group_table_read( service_group_name ); if( NULL == service_group ) { DPRINTFE( "Failed to read service group (%s), error=%s.", service_group_name, sm_error_str(SM_NOT_FOUND) ); return( SM_FAILED ); } _seqnum++; snprintf( &(env->seqnum_str[0]), sizeof(env->seqnum_str), "%lu", _seqnum ); snprintf( &(env->service_group_name[0]), SM_SERVICE_GROUP_NAME_MAX_CHAR, "%s", service_group->name ); env->service_group_desired_state = service_group->desired_state; env->service_group_state = service_group->state; env->service_group_notification = notification; service_domain_member = sm_service_domain_member_table_read_service_group( service_group->name ); if( NULL != service_domain_member ) { if( '\0' != service_domain_member->service_group_aggregate[0] ) { sm_service_domain_member_table_foreach_service_group_aggregate( service_domain_member->name, service_domain_member->service_group_aggregate, user_data, sm_service_group_notification_service_group_aggregate ); switch( aggregate_state ) { case SM_SERVICE_GROUP_STATE_ACTIVE: if(( SM_SERVICE_GROUP_STATE_GO_ACTIVE == transition_state )|| ( SM_SERVICE_GROUP_STATE_GO_STANDBY == transition_state )|| ( SM_SERVICE_GROUP_STATE_DISABLING == transition_state )) aggregate_state = transition_state; break; case SM_SERVICE_GROUP_STATE_STANDBY: if(( SM_SERVICE_GROUP_STATE_GO_STANDBY == transition_state )|| ( SM_SERVICE_GROUP_STATE_DISABLING == transition_state )) aggregate_state = transition_state; break; case SM_SERVICE_GROUP_STATE_DISABLED: if( SM_SERVICE_GROUP_STATE_DISABLING == transition_state ) aggregate_state = transition_state; break; case SM_SERVICE_GROUP_STATE_UNKNOWN: aggregate_state = transition_state; break; default: // Ignore break; } env->part_of_aggregate = true; snprintf( &(env->service_group_aggregate_name[0]), SM_SERVICE_GROUP_AGGREGATE_NAME_MAX_CHAR, "%s", service_domain_member->service_group_aggregate ); env->service_group_aggregate_desired_state = aggregate_desired_state; env->service_group_aggregate_state = aggregate_state; } } return( SM_OKAY ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Setup Environment // ============================================== static SmErrorT sm_service_group_notification_setup_env( SmNotificationEnvT* env ) { if( 0 > setenv( "NOTIFICATION_SEQNUM", env->seqnum_str, 1 ) ) { DPRINTFE( "Failed to set environment variable (NOTIFICATION_SEQNUM), " "error=%s.", strerror( errno ) ); return( SM_FAILED ); } if( 0 > setenv( "SERVICE_GROUP_NAME", env->service_group_name, 1 ) ) { DPRINTFE( "Failed to set environment variable (SERVICE_GROUP_NAME), " "error=%s.", strerror( errno ) ); return( SM_FAILED ); } if( 0 > setenv( "SERVICE_GROUP_DESIRED_STATE", sm_service_group_state_str( env->service_group_desired_state), 1 ) ) { DPRINTFE( "Failed to set environment variable " "(SERVICE_GROUP_DESIRED_STATE), error=%s.", strerror( errno ) ); return( SM_FAILED ); } if( 0 > setenv( "SERVICE_GROUP_STATE", sm_service_group_state_str(env->service_group_state), 1 ) ) { DPRINTFE( "Failed to set environment variable (SERVICE_GROUP_STATE), " "error=%s.", strerror( errno ) ); return( SM_FAILED ); } if( 0 > setenv( "SERVICE_GROUP_NOTIFICATION", sm_service_group_notification_str( env->service_group_notification), 1 ) ) { DPRINTFE( "Failed to set environment variable " "(SERVICE_GROUP_NOTIFICATION), error=%s.", strerror( errno ) ); return( SM_FAILED ); } if( env->part_of_aggregate ) { if( 0 > setenv( "SERVICE_GROUP_AGGREGATE_NAME", env->service_group_aggregate_name, 1 ) ) { DPRINTFE( "Failed to set environment variable " "(SERVICE_GROUP_AGGREGATE_NAME), error=%s.", strerror( errno ) ); return ( SM_FAILED ); } if( 0 > setenv( "SERVICE_GROUP_AGGREGATE_DESIRED_STATE", sm_service_group_state_str( env->service_group_aggregate_desired_state), 1 ) ) { DPRINTFE( "Failed to set environment variable " "(SERVICE_GROUP_AGGREGATE_DESIRED_STATE), error=%s.", strerror( errno ) ); return ( SM_FAILED ); } if( 0 > setenv( "SERVICE_GROUP_AGGREGATE_STATE", sm_service_group_state_str( env->service_group_aggregate_state), 1 ) ) { DPRINTFE( "Failed to set environment variable " "(SERVICE_GROUP_AGGREGATE_STATE), error=%s.", strerror( errno ) ); return ( SM_FAILED ); } } return( SM_OKAY ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Run // ================================ static SmErrorT sm_service_group_notification_run( char service_group_name[], SmServiceGroupNotificationT notification, int* process_id ) { pid_t pid; struct stat stat_data; int result; char program_name[80]; SmNotificationEnvT env; SmErrorT error; *process_id = -1; memset( &env, 0, sizeof(SmNotificationEnvT) ); if( 0 > access( SM_NOTIFICATION_SCRIPT, F_OK | X_OK ) ) { DPRINTFE( "Service group notification script access failed, " "error=%s.", strerror( errno ) ); return( SM_FAILED ); } if( 0 > stat( SM_NOTIFICATION_SCRIPT, &stat_data ) ) { DPRINTFE( "Service group notification script stat failed, " "error=%s.", strerror( errno ) ); return( SM_FAILED ); } if( 0 >= stat_data.st_size ) { DPRINTFE( "Service group notification script has zero size." ); return( SM_FAILED ); } error = sm_service_group_notification_get_env( service_group_name, notification, &env ); if( SM_OKAY != error ) { DPRINTFE( "Failed to get environment for service group (%s) " "notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), sm_error_str( error ) ); return( SM_FAILED ); } pid = fork(); if( 0 > pid ) { DPRINTFE( "Failed to fork notification process for service group " "(%s), error=%s.", service_group_name, strerror( errno ) ); return( SM_FAILED ); } else if( 0 == pid ) { // Child process. struct rlimit file_limits; DPRINTFD( "Child notification process created for service group " "(%s).", service_group_name ); if( 0 > setpgid( 0, 0 ) ) { DPRINTFE( "Failed to set notification process group id for " "service group (%s) notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), strerror( errno ) ); exit( SM_NOTIFICATION_SCRIPT_FAILURE ); } if( 0 > getrlimit( RLIMIT_NOFILE, &file_limits ) ) { DPRINTFE( "Failed to get file limits for service group (%s) " "notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), strerror( errno ) ); exit( SM_NOTIFICATION_SCRIPT_FAILURE ); } unsigned int fd_i; for( fd_i=0; fd_i < file_limits.rlim_cur; ++fd_i ) { close( fd_i ); } if( 0 > open( "/dev/null", O_RDONLY ) ) { DPRINTFE( "Failed to open stdin to /dev/null for service group " "(%s) notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), strerror( errno ) ); } if( 0 > open( "/dev/null", O_WRONLY ) ) { DPRINTFE( "Failed to open stdout to /dev/null for service group " "(%s) notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), strerror( errno ) ); } if( 0 > open( "/dev/null", O_WRONLY ) ) { DPRINTFE( "Failed to open stderr to /dev/null for service group " "(%s) notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), strerror( errno ) ); } result = setpriority( PRIO_PROCESS, getpid(), -1 ); if( 0 > result ) { DPRINTFE( "Failed to set priority of process, error=%s.", strerror( errno ) ); exit( SM_NOTIFICATION_SCRIPT_FAILURE ); } error = sm_service_group_notification_setup_env( &env ); if( SM_OKAY != error ) { DPRINTFE( "Failed to setup environment for service group (%s) " "notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), sm_error_str( error ) ); exit( SM_NOTIFICATION_SCRIPT_FAILURE ); } snprintf( program_name, sizeof(program_name), "%s", SM_NOTIFICATION_SCRIPT ); char* argv[] = {program_name, NULL}; if( 0 > execv( program_name, argv ) ) { DPRINTFE( "Failed to exec command for service group (%s) " "notification (%s), error=%s.", service_group_name, sm_service_group_notification_str(notification), strerror( errno ) ); } exit( SM_NOTIFICATION_SCRIPT_FAILURE ); } else { // Parent process. *process_id = (int) pid; DPRINTFD( "Child notification process (%i) created for service " "group (%s) notification (%s).", *process_id, service_group_name, sm_service_group_notification_str(notification) ); } return( SM_OKAY ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Timeout // ==================================== static bool sm_service_group_notification_timeout( SmTimerIdT timer_id, int64_t user_data ) { int64_t id = user_data; SmServiceGroupT* service_group; SmErrorT error; service_group = sm_service_group_table_read_by_id( id ); if( NULL == service_group ) { DPRINTFE( "Failed to read service group, error=%s.", sm_error_str(SM_NOT_FOUND) ); return( false ); } if( -1 == service_group->notification_pid ) { DPRINTFE( "Service group (%s) notification script not running.", service_group->name ); return( false ); } DPRINTFI( "Notification (%s) timeout for service group (%s).", service_group->notification_str, service_group->name ); error = sm_service_group_notification_do_abort( service_group->name, service_group->notification_pid ); if( SM_OKAY != error ) { DPRINTFE( "Failed to abort notification script for service group " "(%s), error=%s.", service_group->name, sm_error_str( error ) ); } service_group->notification_pid = -1; service_group->notification_timer_id = SM_TIMER_ID_INVALID; service_group->notification_complete = false; service_group->notification_failed = true; service_group->notification_timeout = true; error = sm_service_group_fsm_event_handler( service_group->name, SM_SERVICE_GROUP_EVENT_NOTIFICATION_TIMEOUT, NULL, "notification script timed out" ); if( SM_OKAY != error ) { DPRINTFE( "Failed to notify service group (%s) fsm notification " "timed out, error=%s.", service_group->name, sm_error_str( error ) ); abort(); } return( false ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Complete // ===================================== static void sm_service_group_notification_complete( pid_t pid, int exit_code, int64_t user_data ) { SmServiceGroupT* service_group; SmServiceGroupEventT event; char reason_text[SM_SERVICE_GROUP_REASON_TEXT_MAX_CHAR] = ""; SmErrorT error; service_group = sm_service_group_table_read_by_notification_pid( (int) pid ); if( NULL == service_group ) { DPRINTFE( "Failed to query service group based on pid (%i), error=%s.", (int) pid, sm_error_str(SM_NOT_FOUND) ); return; } if( -1 == service_group->notification_pid ) { DPRINTFE( "Service group (%s) not running notification script.", service_group->name ); return; } if( SM_TIMER_ID_INVALID != service_group->notification_timer_id ) { error = sm_timer_deregister( service_group->notification_timer_id ); if( SM_OKAY == error ) { DPRINTFD( "Timer stopped for notification (%s) for service " "group (%s).", service_group->notification_str, service_group->name ); } else { DPRINTFE( "Failed to stop timer for notification (%s) for " "service group (%s), error=%s.", service_group->notification_str, service_group->name, sm_error_str( error ) ); } } service_group->notification_pid = -1; service_group->notification_timer_id = SM_TIMER_ID_INVALID; if( SM_NOTIFICATION_SCRIPT_SUCCESS == exit_code ) { DPRINTFI( "Notification (%s) for service group (%s) passed.", service_group->notification_str, service_group->name ); service_group->notification_complete = true; service_group->notification_failed = false; service_group->notification_timeout = false; event = SM_SERVICE_GROUP_EVENT_NOTIFICATION_SUCCESS; } else { DPRINTFI( "Notification (%s) for service group (%s) failed.", service_group->notification_str, service_group->name ); service_group->notification_complete = true; service_group->notification_failed = true; service_group->notification_timeout = false; event = SM_SERVICE_GROUP_EVENT_NOTIFICATION_FAILED; snprintf( reason_text, sizeof(reason_text), "notification script " "failed" ); } error = sm_service_group_fsm_event_handler( service_group->name, event, NULL, reason_text ); if( SM_OKAY != error ) { DPRINTFE( "Failed to notify service group (%s) fsm, error=%s.", service_group->name, sm_error_str( error ) ); abort(); } } // **************************************************************************** // **************************************************************************** // Service Group Notification - Notify // =================================== SmErrorT sm_service_group_notification_notify( SmServiceGroupT* service_group, SmServiceGroupNotificationT notification ) { const char* notification_str; char timer_name[80] = ""; int process_id = -1; int timeout_in_ms = SM_NOTIFICATION_SCRIPT_TIMEOUT_IN_MS; SmTimerIdT timer_id = SM_TIMER_ID_INVALID; SmErrorT error; notification_str = sm_service_group_notification_str(notification); // Run notification script. error = sm_service_group_notification_run( service_group->name, notification, &process_id ); if( SM_OKAY != error ) { DPRINTFE( "Failed to run notification script for service group (%s), " "error=%s.", service_group->name, sm_error_str( error ) ); return( error ); } // Register for notification script exit. error = sm_process_death_register( process_id, true, sm_service_group_notification_complete, 0 ); if( SM_OKAY != error ) { DPRINTFE( "Failed to register for notification completion for " "service group (%s), error=%s.", service_group->name, sm_error_str( error ) ); abort(); } // Create timer for notification completion. snprintf( timer_name, sizeof(timer_name), "%s %s notification ", service_group->name, notification_str ); error = sm_timer_register( timer_name, timeout_in_ms, sm_service_group_notification_timeout, service_group->id, &timer_id ); if( SM_OKAY != error ) { DPRINTFE( "Failed to create a timer for notification for service " "group (%s), error=%s.", service_group->name, sm_error_str( error ) ); abort(); } service_group->notification = notification; service_group->notification_str = notification_str; service_group->notification_pid = process_id; service_group->notification_timer_id = timer_id; service_group->notification_complete = false; service_group->notification_failed = false; service_group->notification_timeout = false; DPRINTFI( "Started notification (%s) process (%i) for service group " "(%s).", service_group->notification_str, process_id, service_group->name ); return( SM_OKAY ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Abort // ================================== SmErrorT sm_service_group_notification_abort( SmServiceGroupT* service_group ) { const char* notification_str; SmErrorT error; notification_str = sm_service_group_notification_str(service_group->notification); DPRINTFI( "Aborting notification (%s) for service group (%s).", notification_str, service_group->name ); if( -1 != service_group->notification_pid ) { error = sm_service_group_notification_do_abort( service_group->name, service_group->notification_pid ); if( SM_OKAY != error ) { DPRINTFE( "Failed to abort notification (%s) for service group " "(%s), error=%s.", notification_str, service_group->name, sm_error_str( error ) ); return( error ); } } if( SM_TIMER_ID_INVALID != service_group->notification_timer_id ) { error = sm_timer_deregister( service_group->notification_timer_id ); if( SM_OKAY != error ) { DPRINTFE( "Cancel notification timer for service group (%s) " "failed, error=%s.", service_group->name, sm_error_str( error ) ); return( error ); } } service_group->notification = SM_SERVICE_GROUP_NOTIFICATION_NIL; service_group->notification_str = NULL; service_group->notification_pid = -1; service_group->notification_timer_id = SM_TIMER_ID_INVALID; service_group->notification_complete = false; service_group->notification_failed = false; service_group->notification_timeout = false; return( SM_OKAY ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Initialize // ======================================= SmErrorT sm_service_group_notification_initialize( void ) { return( SM_OKAY ); } // **************************************************************************** // **************************************************************************** // Service Group Notification - Finalize // ===================================== SmErrorT sm_service_group_notification_finalize( void ) { return( SM_OKAY ); } // ****************************************************************************