/* $Id: apcsnmp.c 303 2007-09-27 07:07:25Z fil $ */ /* Copyright 2007 Philip Gwyn. All rights reserved. */ /* Based on http://net-snmp.sourceforge.net/wiki/index.php/TUT:Simple_Application Copyright the Net-SNMP team */ /* This is a small library to talk to an APC PDU via SNMP. It will automatically detect and work with new (AP79xx) MIB and the older (AP96xx) MIB. There is also a userPDU that may be used to control other SNMP-aware PDUs. If you have one, and can find the 4-5 OIDs needed, please e-mail me so I can add them here. It is intended to provide 2 actions : - turn an outlet on - turn an outlet off Outlets names are fetched from the PDU. Wildcard matching is provided. This file is maintained by: Philip Gwyn */ /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #ifdef STONITH_MODULES /* Following is set by net-snmp */ #undef FREE #include "stonith_plugin_common.h" /* These next 2 are created in apcsnmp-plugin.c */ /* Needed by LOG() */ extern const PILPluginImports* PluginImports; /* Needed for Debug */ extern const PILPluginOps* PluginOps; #define Debug (PluginOps!=NULL && PluginOps->getdebuglevel() >= PIL_INFO) #else #define Debug 1 #endif #include "apcsnmp.h" /*******************************************************************/ static MID_t masterSwitchV2_oid = { /* AP9606 */ /* PowerNet-MIB::sPDUoutletControlTableSize.0 */ ".1.3.6.1.4.1.318.1.1.4.4.1.0", /* PowerNet-MIB::sPDUoutletCtlName */ ".1.3.6.1.4.1.318.1.1.4.4.2.1.4.%i", /* PowerNet-MIB::sPDUoutletCtl */ ".1.3.6.1.4.1.318.1.1.4.4.2.1.3.%i", /* PowerNet-MIB::sPDUoutletCtl */ ".1.3.6.1.4.1.318.1.1.4.4.2.1.3.%i", /* outletOn(1) */ "1", /* outletOff(2) */ "2", /* outletReset(3) */ "3" }; static PDU_t masterSwitchV2 = { "masterSwitchV2", /* PowerNet-MIB::masterSwitchV2 */ ".1.3.6.1.4.1.318.1.3.4.2", &masterSwitchV2_oid, 0 }; static MID_t masterSwitchrPDU_oid = { /* AP7900 */ /* PowerNet-MIB::rPDUoutletDevNumCntrloutlets.0 */ ".1.3.6.1.4.1.318.1.1.12.3.1.3.0", /* PowerNet-MIB::rPDUoutletControloutletName */ ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2.%i", /* PowerNet-MIB::rPDUoutletStatusoutletState */ ".1.3.6.1.4.1.318.1.1.12.3.5.1.1.4.%i", /* PowerNet-MIB::rPDUoutletControloutletCommand */ ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%i", /* immediateOn(1) / outletStatusOn(1) */ "1", /* immediateOff(2) / outletStatusOff(2) */ "2", /* immediateReset(3) / outletStatusReset(3) */ "3" }; static PDU_t masterSwitchrPDU = { "masterSwitchrPDU", /* PowerNet-MIB::masterSwitchrPDU */ ".1.3.6.1.4.1.318.1.3.4.5", &masterSwitchrPDU_oid, 0 }; static MID_t userPDU_oid = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; PDU_t userPDU = { "UserPDU", NULL, &userPDU_oid }; static PDU_t *PDU_config[4] = { &userPDU, &masterSwitchrPDU, &masterSwitchV2, NULL }; static const char *no_name = "PDU"; /*******************************************************************/ static void report_error( host_t *host, const char *fn, const char *msg); static int oid_convert( host_t *host, OID_t *oid ); static req_t *req_start ( host_t *host, int type ); static void req_stop ( host_t *host, req_t *req ); static int snmp_req_send ( host_t *host, req_t *req ); static netsnmp_pdu *snmp_get ( host_t *host, const char *OID, int type ); static netsnmp_pdu *snmp_set ( host_t *host, OID_t *OID, char type, const char *value ); /* static void snmp_pdu_dump_vars ( netsnmp_pdu *pdu ); */ static netsnmp_pdu *snmp_sysDescr ( host_t *host ); static netsnmp_pdu *snmp_sysObjectID ( host_t *host ); static netsnmp_pdu *snmp_sysName ( host_t *host ); static int outlet_get( host_t *host, outlet_t *outlet ); static int outlet_set( host_t *host, outlet_t *outlet, const char *oid ); static int outlet_name_match( char *name, const char *want ); static void outlet_init( host_t *host, outlet_t *outlet, char *name, int n ); static int host_outlet_find( host_t *host, const char *outlet, outlet_t *outlets ); static int host_outlet_check( host_t *host, outlet_t *outlets, int op ); static int var2oid( char *dest, netsnmp_variable_list *var, size_t max ); static int host_match_oid ( host_t *host, netsnmp_variable_list *var ); static int host_outlet_control( host_t *host, outlet_t *outlets, int op ); /*******************************************************************/ static void report_error( host_t *host, const char *fn, const char *msg) { int snmperr = 0; int cliberr = 0; char *errstr; snmp_error( &host->session, &cliberr, &snmperr, &errstr); LOG( PIL_CRIT, "%s: %s: %s (cliberr: %i, snmperr: %i, error: %s)", fn, ( host->sysName != NULL ? host->sysName : "PDU" ), msg, cliberr, snmperr, errstr); free(errstr); } /******************************************************************* * Initialize the SNMP library. * This creates a SNMP session for the host */ int host_init ( host_t *host ) { init_snmp( DEVICE ); host->ss = NULL; SOCK_STARTUP; /* * Initialize a "session" that defines who we're going to talk to */ snmp_sess_init( &host->session ); host->session.peername = strdup( host->name ); if( host->session.peername == NULL ) { LOG( PIL_CRIT, "Failed to dup peername: [%i] %s", errno, strerror( errno ) ); return APC_ERROR; } if( host->port <= 0 ) host->port = 161; host->session.remote_port = host->port; /* set the SNMP version number */ host->session.version = SNMP_VERSION_1; /* set the SNMPv1 community name used for authentication */ host->session.community = strdup( host->community ); if( host->session.community == NULL ) { LOG( PIL_CRIT, "Failed to dup community: [%i] %s", errno, strerror( errno ) ); host_deinit( host ); return APC_ERROR; } host->session.community_len = strlen( host->session.community ); /* set up defaults */ host->session.retries = 5; host->session.timeout = 1000000; /* is this in milliseconds? */ /* establish the session */ host->ss = snmp_open( &host->session ); host->open = 1; if ( !host->ss ) { report_error( host, __FUNCTION__, "can't open snmp session" ); host_deinit( host ); return APC_ERROR; } /* Paranoid */ host->sysName = NULL; return APC_OK; } /*******************************************************************/ /* Free any memory used by the current host */ void host_deinit ( host_t *host ) { if( host->session.peername != NULL ) { free( host->session.peername ); host->session.peername = NULL; } if( host->session.community != NULL ) { free( host->session.community ); host->session.community = NULL; } if( host->ss != NULL) { snmp_close( host->ss ); host->ss = NULL; } if( host->open ) { snmp_close( &host->session ); host->open = 0; } if( host->sysName != NULL ) { free( host->sysName ); host->sysName = NULL; } SOCK_CLEANUP; } /*******************************************************************/ /* Convert an OID from text into binary */ static int oid_convert( host_t *host, OID_t *oid ) { oid->len = MAX_OID_LEN; oid->oid[0] = 0; if (!snmp_parse_oid( oid->text, oid->oid, &oid->len ) ) { report_error( host, __FUNCTION__, oid->text ); return APC_ERROR; } return APC_OK; } /*******************************************************************/ static req_t *req_start ( host_t *host, int type ) { req_t *req; req = malloc( sizeof( req_t ) ); if( req == NULL ) { LOG( PIL_CRIT, "Failed malloc: [%i] %s", errno, strerror( errno ) ); return req; } req->resp = NULL; req->req = snmp_pdu_create( type ); return req; } /*******************************************************************/ /* Deallocate any data used by a request Note that snmp_get and snmp_set will set ->resp to NULL because it will be returned to their callers, who must take care of freeing it */ static void req_stop ( host_t *host, req_t *req ) { if( req->req != NULL ) { /* snmp_sync_response does the free for us */ /* snmp_free_pdu( req->req ); */ req->req = NULL; } if( req->resp != NULL ) { snmp_free_pdu( req->resp ); req->resp = NULL; } free( req ); } /*******************************************************************/ /* Send an SNMP request. Deal with error responses. Returns the net-snmp status code or -1 on error. */ static int snmp_req_send ( host_t *host, req_t *req ) { int status; /* * Send the Request out. */ status = snmp_synch_response( host->ss, req->req, &req->resp ); /* * Process the response. */ if (status == STAT_SUCCESS && req->resp->errstat == SNMP_ERR_NOERROR) { return status; } /* * FAILURE: print what went wrong! */ if (status == STAT_SUCCESS) { LOG( PIL_CRIT, "Error in response packet from %s, reason %ld [%s].", host->sysName, req->resp->errstat, snmp_errstring( req->resp->errstat)); } else if (status == STAT_TIMEOUT) { report_error( host, __FUNCTION__, "Timeout" ); } else { report_error( host, __FUNCTION__, "Error sending/receiving" ); } return -1; } /*******************************************************************/ /* Get a value from the PDU. Returns an netsnmp_pdu object (that the caller must free) or NULL on error. The error will already have been reported. */ netsnmp_pdu *snmp_get ( host_t *host, const char *OID, int type ) { int status, l; req_t *req; netsnmp_pdu *resp = NULL; OID_t oid; req = req_start( host, SNMP_MSG_GET ); if( req == NULL ) return NULL; l = strlen( OID ); if ( l > MAX_STRING-1) { LOG( PIL_CRIT, "OID is too long (%i)", l ); req_stop( host, req ); return NULL; } strcpy( oid.text, OID ); if ( APC_OK != oid_convert( host, &oid ) ) { req_stop( host, req ); return NULL; } snmp_add_null_var( req->req, oid.oid, oid.len ); status = snmp_req_send( host, req ); if( status == STAT_SUCCESS ) { resp = req->resp; req->resp = NULL; if( type && resp->variables->type != type ) { LOG( PIL_CRIT, "SNMP request '%s' didn't return the right type " "(%i vs %i)", oid.text, type, resp->variables->type ); req->resp = resp; resp = NULL; } } req_stop( host, req ); return resp; } /*******************************************************************/ /* Set a value on the PDU. Returns an netsnmp_pdu object (that the caller must free) or NULL on error. The error will already have been reported. */ static netsnmp_pdu *snmp_set ( host_t *host, OID_t *OID, char type, const char *value ) { int status; req_t *req; netsnmp_pdu *resp = NULL; req = req_start( host, SNMP_MSG_SET ); if( req == NULL ) return NULL; if ( !OID->len && APC_OK != oid_convert( host, OID ) ) { req_stop( host, req ); return NULL; } snmp_add_var( req->req, OID->oid, OID->len, type, value); if( Debug ) LOG( PIL_DEBUG, "snmp_set OID=%s type=%c value=%s", OID->text, type, value ); status = snmp_req_send( host, req ); if( status == STAT_SUCCESS ) { resp = req->resp; req->resp = NULL; } else { LOG( PIL_CRIT, "%s refused snmpset %s %c %s", host->sysName, OID->text, type, value ); LOG( PIL_CRIT, "%s: Does community '%s' have write permissions?", host->sysName, host->community ); } req_stop( host, req ); return resp; } /*******************************************************************/ /* Utility function */ #if 0 static void snmp_pdu_dump_vars ( netsnmp_pdu *pdu ) { netsnmp_variable_list *var; int count=1; /* manipuate the information ourselves */ for( var = pdu->variables; var; var = var->next_variable ) { if ( var->type == ASN_OCTET_STR ) { char *sp = (char *)malloc( 1 + var->val_len ); memcpy(sp, var->val.string, var->val_len); sp[var->val_len] = '\0'; printf( "#%d: %s\n", count, sp ); free(sp); } else if( var->type == ASN_INTEGER ) { printf( "#%d: %li\n", count, *var->val.integer ); } else if( var->type == ASN_NULL ) { printf( "#%d: %p\n", count, NULL ); } else { printf("value #%d is of type (%i)\n", count, var->type ); } count++; } } #endif /*******************************************************************/ /* Find the system description. This is a long string like AP7900 : APC Web/SNMP Management Card (MB:v3.8.6 PF:v3.3.4 PN:apc_hw02_aos_334.bin AF1:v3.3.3 AN1:apc_hw02_rpdu_333.bin MN:AP7900 HR:B2 SN: ZA0722007382 MD:05/30/2007) AP9606 : APC Web/SNMP Management Card (MB:v3.0.5 PF:v2.0.1 PN:aos201.bin AF1:v2.0.1 AN1:ms201.bin MN: AP9606 HR: G9 SN: WA0012008696 MD: 03/15/2000) */ static netsnmp_pdu *snmp_sysDescr ( host_t *host ) { /* SNMPv2-MIB::sysDescr.0 */ return snmp_get( host, ".1.3.6.1.2.1.1.1.0", ASN_OCTET_STR ); } /*******************************************************************/ /* Find the system name. This is a user configurable string. */ static netsnmp_pdu *snmp_sysName ( host_t *host ) { /* SNMPv2-MIB::sysName.0 */ return snmp_get( host, ".1.3.6.1.2.1.1.5.0", ASN_OCTET_STR ); } /*******************************************************************/ /* Find the system OID, that is the MIB used by the PDU. This function is used to identify the PDU and select a set of OIDs to use. */ static netsnmp_pdu *snmp_sysObjectID ( host_t *host ) { /* SNMPv2-MIB::sysObjectID.0 */ return snmp_get( host, ".1.3.6.1.2.1.1.2.0", ASN_OBJECT_ID ); } /*******************************************************************/ /* Initialise an outlet_t */ static void outlet_init( host_t *host, outlet_t *outlet, char *name, int n ) { outlet->n = n; strncpy( outlet->name, name, strlen( name ) ); snprintf( outlet->outletState.text, MAX_STRING, host->PDU->OID->outletState, outlet->n ); outlet->outletState.len = 0; /* length of the parsed OID, not of outletState.text */ snprintf( outlet->outletCtl.text, MAX_STRING, host->PDU->OID->outletCtl, outlet->n ); outlet->outletCtl.len = 0; /* length of the parsed OID, not of outletCtl.text */ } /*******************************************************************/ /* Get the current state of an outlets on the PDU. Returns the state, or APC_ERROR. */ static int outlet_get( host_t *host, outlet_t *outlet ) { netsnmp_variable_list *var; netsnmp_pdu *resp; int rv = APC_ERROR; /* Paranoia */ if( host->PDU == NULL ) { LOG( PIL_CRIT, "You must run host_identify() %s", "first" ); return APC_ERROR; } /* send the request */ resp = snmp_get( host, outlet->outletState.text, ASN_INTEGER ); if( resp == NULL ) return APC_ERROR; var = resp->variables; rv = *var->val.integer; snmp_free_pdu( resp ); return rv; } /*******************************************************************/ /* Send a command to the outlet. Returns the integer value the PDU responded with or APC_ERROR. On APC PDUs, the integer response is the same as the command sent. So seting "3" (Reset) should generate a 3 response. */ static int outlet_set( host_t *host, outlet_t *outlet, const char *oid ) { netsnmp_variable_list *var; netsnmp_pdu *resp; OID_t ctl_oid; int rv = APC_ERROR; /* Paranoia */ if( host->PDU == NULL ) { LOG( PIL_CRIT, "You must run host_identify() %s", "first" ); return APC_ERROR; } /* Create the OID */ if ( !outlet->outletCtl.len && APC_OK != oid_convert( host, &outlet->outletCtl ) ) { return APC_ERROR; } /* send the request */ resp = snmp_set( host, &outlet->outletCtl, 'i', oid ); if( resp == NULL ) return APC_ERROR; var = resp->variables; if( var->type == ASN_INTEGER ) { rv = *var->val.integer; } else { LOG( PIL_CRIT, "SNMP '%s' didn't return ASN_INTEGER", ctl_oid.text ); } snmp_free_pdu( resp ); return rv; } /*******************************************************************/ /* See if 'name' matches 'want'. 'want' may contain the wildcard */ static int outlet_name_match( char *name, const char *want ) { int wl = strlen( want ); int nl = strlen( name ); if( want[wl-1] == '*' ) { /* wildcard match */ wl--; if( nl < wl ) /* Shorter then what we want */ return APC_NOT; } else if( nl != wl ) return APC_NOT; if( 0 == strncasecmp( name, want, wl ) ) return APC_OK; else return APC_NOT; } /*******************************************************************/ /* Convert a OID array (returned by snmp_get) into a string This should be replaced with something from net-snmp, asn_build_string ?? */ static int var2oid( char *dest, netsnmp_variable_list *var, size_t max ) { int q, len, width = 11; /* maximum width of a long int '-' + 10 digits */ char *out = dest; oid *in = var->val.objid; max -= width; max -= 1; len = var->val_len / sizeof( oid ); for( q=0; q max ) { /* this is very paranoid */ LOG( PIL_CRIT, "OID is too long (%i)", len ); return APC_ERROR; } snprintf( out, width, ".%li", in[q] ); out += strlen( out ); } return APC_OK; } /*******************************************************************/ /* Match the OID returned by sysObjectId against the list of known OIDs. Sets host->PDU to the matching definition. Returns APC_OK if there is a match, APC_NOT if not. APC_ERROR on errors */ static int host_match_oid ( host_t *host, netsnmp_variable_list *var ) { char OID[MAX_STRING]; int len, q, rv; OID[0] = '\0'; rv = var2oid( OID, var, sizeof( OID ) ); if( rv != APC_OK ) return APC_ERROR; len = strlen( OID ); for( q=0; PDU_config[q] != NULL ; q++ ) { PDU_t *pdu = PDU_config[q]; if( pdu->sysOID != NULL ) { int l = strlen( pdu->sysOID ); if( l < len ) l = len; if( 0 == strncmp( OID, pdu->sysOID, l ) ) { host->PDU = pdu; return APC_OK; } } } LOG( PIL_CRIT, "Unable to find a PDU definition that matches the OID %s", OID ); host->PDU = NULL; return APC_NOT; } /*******************************************************************/ /* Identify a given PDU. The sysName and PDU members of host will be set to the right values. Returns APC_OK or APC_ERROR. */ int host_identify ( host_t *host ) { int rv = APC_OK; netsnmp_pdu *resp; netsnmp_variable_list *var; /* Paranoid */ host->PDU = NULL; if( host->sysName != NULL ) { free( host->sysName ); host->sysName = NULL; } if( Debug ) LOG( PIL_DEBUG, "Identifying host %s", host->name ); /* Fetch the system's OID */ resp = snmp_sysObjectID( host ); if( resp == NULL ) { LOG( PIL_WARN, "Failed to get sysObjectID from host %s", host->name ); return APC_ERROR; } /* Match the result with an entry in PDU_config */ rv = host_match_oid( host, resp->variables ); snmp_free_pdu( resp ); if( rv == APC_ERROR ) return APC_ERROR; else if( rv == APC_OK ) { LOG( PIL_INFO, "%s uses sysOID '%s', compatible with '%s'", host->name, host->PDU->sysOID, host->PDU->name ); } /* TODO: if rv == APC_NOT, do we skip out early? */ /* Fetch the system's name */ resp = snmp_sysName( host ); if( resp == NULL ) { LOG( PIL_WARN, "Failed to get sysName from host %s", host->name ); host->sysName = strdup( no_name ); if( host->sysName == NULL ) { LOG( PIL_CRIT, "Failed to set the default sysName: [%i] %s", errno, strerror( errno ) ); return APC_ERROR; } /* we can continue w/ the default sysName */ return APC_OK; } var = resp->variables; host->sysName = (char *)malloc( var->val_len +1 ); if( host->sysName == NULL ) { LOG( PIL_CRIT, "Failed to copy sysName: [%i] %s", errno, strerror( errno ) ); rv = APC_ERROR; } else { memcpy( host->sysName, var->val.string, var->val_len ); host->sysName[ var->val_len ] = '\0'; LOG( PIL_INFO, "%s has sysName '%s'", host->name, host->sysName ); } snmp_free_pdu( resp ); return rv; } /*******************************************************************/ /* Find the number of outlets a PDU has. Sets the number of outlets at host->PDU->outlets. Returns the number of outlets or APC_ERROR */ int host_outlets ( host_t *host ) { netsnmp_variable_list *var; netsnmp_pdu *resp; /* Paranoia */ if( host->PDU == NULL ) { LOG( PIL_CRIT, "You must run host_identify() %s", "first" ); return APC_ERROR; } host->PDU->outlets = 0; resp = snmp_get( host, host->PDU->OID->outletPorts, ASN_INTEGER ); if( resp == NULL ) { LOG( PIL_WARN, "Failed to get OutletPorts from host %s", host->name ); return APC_ERROR; } var = resp->variables; host->PDU->outlets = *var->val.integer; if( Debug ) LOG( PIL_DEBUG, "%s has %i outlets", host->sysName, host->PDU->outlets ); snmp_free_pdu( resp ); return host->PDU->outlets; } /*******************************************************************/ /* Fetch the name of an outlet. The name is copied into name (which must be at least MAX_STRING long) */ int host_outlet_name( host_t *host, int q, char *name ) { char name_oid[MAX_STRING]; netsnmp_variable_list *var; netsnmp_pdu *resp; name[0] = '\0'; /* Paranoia */ if( host->PDU == NULL ) { LOG( PIL_CRIT, "You must run host_identify() %s", "first" ); return APC_ERROR; } if( host->PDU->outlets == 0 && host_outlets( host ) == APC_ERROR ) { return APC_ERROR; } snprintf( name_oid, MAX_STRING, host->PDU->OID->outletNames, q ); resp = snmp_get( host, name_oid, ASN_OCTET_STR ); if( resp == NULL ) { LOG( PIL_CRIT, "Failed to get outletName #%i from %s", q, host->name ); return APC_ERROR; } var = resp->variables; if( var->val_len > MAX_STRING - 1 ) { LOG( PIL_CRIT, "Failed to get outletName #%i. String is too long (%i)", q, var->val_len ); return APC_ERROR; } memcpy( name, var->val.string, var->val_len ); name[ var->val_len ] = '\0'; if( Debug ) LOG( PIL_DEBUG, "%s outlet #%i = %s", host->sysName, q, name ); snmp_free_pdu( resp ); return APC_OK; } /*******************************************************************/ /* Find the outlets on "host" that match "outlet". Returns the number of outlets that matched, or APC_ERROR. Fills *outlet with inited outlet_t objects, up to the number of outlets matched. The one after will have n=-1. This means it will find n more then MAX_OUTLETS-1 outlets. */ static int host_outlet_find( host_t *host, const char *outlet, outlet_t *outlets ) { int q, rv, o_l = 0; outlets[ o_l ].n = -1; /* Paranoia */ if( host->PDU == NULL ) { LOG( PIL_CRIT, "You must run host_identify() %s", "first" ); return APC_ERROR; } if( host->PDU->outlets == 0 && host_outlets( host ) == APC_ERROR ) { return APC_ERROR; } /* get a list of outlets that match "outlet" */ /* Outlets are numbered 1 to 8 */ if( Debug ) LOG( PIL_DEBUG, "%s looking for %s", host->sysName, outlet ); for( q=1; q <= host->PDU->outlets && o_l < (MAX_OUTLETS-1) ; q++ ) { char name[MAX_STRING]; rv = host_outlet_name( host, q, name ); if( APC_OK == rv ) { rv = outlet_name_match( name, outlet ); if( rv == APC_OK ) { if( Debug ) LOG( PIL_DEBUG, "%s match #%i = [%i] '%s'", host->sysName, o_l, q, name ); outlet_init( host, &outlets[ o_l ], name, q ); o_l++; outlets[ o_l ].n = -1; } } } return o_l; } /*******************************************************************/ /* Control all the outlets defined in "outlets" 'op' maybe APC_OP_OFF, APC_OP_RESET. Anything else is considered APC_OP_ON Returns APC_OK or APC_ERROR. */ static int host_outlet_control( host_t *host, outlet_t *outlets, int op ) { int q, rv; outlet_t *outlet; const char *action, *ctl; /* Paranoia */ if( host->PDU == NULL ) { LOG( PIL_CRIT, "You must run host_identify() %s", "first" ); return APC_ERROR; } if( op == APC_OP_OFF ) { action = "turn off"; ctl = host->PDU->OID->ctlOff; } else if ( op == APC_OP_RESET ) { action = "reset"; ctl = host->PDU->OID->ctlReset; } else { /* anything else is "on" */ action = "turn on"; ctl = host->PDU->OID->ctlOn; } if( ctl == NULL ) { LOG( PIL_CRIT, "%s->ctl%s isn't specified!", host->PDU->name, ( op == APC_OP_OFF ? "Off" : op == APC_OP_RESET ? "Reset" : "On" ) ); return APC_ERROR; } for( q=0; outlets[q].n != -1 ; q++ ) { outlet = &outlets[q]; LOG( PIL_INFO, "%s outlet #%i '%s' %s (%s)", host->sysName, outlet->n, outlet->name, action, ctl ); rv = outlet_set( host, outlet, ctl ); if( rv == APC_ERROR ) return APC_ERROR; /* NB : we don't really care what the outlet responded with, as long as we didn't get an SNMP error. For instance, if you send AP6900 a "10", it resondes with badValue, which comes to us as APC_ERROR */ } return APC_OK; } /*******************************************************************/ /* Check to see if all the outlets listed in '*outlets' are in state 'on'. 'on' will be either 1 (on) or 0 (off). It shouldn't be 2 (reset) but even if it is, that counts as waiting for 1 (on). Returns APC_OK (every outlet in the right state) APC_NOT (not every outlet in the right state) APC_ERROR (error condition) */ static int host_outlet_check( host_t *host, outlet_t *outlets, int op ) { int q, ok=APC_OK, /* everything is OK */ cur, /* current value of an outlet */ /* wanted value of all outlets */ want = atoi( op ? host->PDU->OID->ctlOn : host->PDU->OID->ctlOff ); /* Paranoia */ if( host->PDU == NULL ) { LOG( PIL_CRIT, "You must run host_identify() %s", "first" ); return APC_ERROR; } for( q=0; ok==APC_OK && outlets[q].n != -1 ; q++ ) { outlet_t *outlet = &outlets[q]; cur = outlet_get( host, outlet ); if( cur == APC_ERROR ) { LOG( PIL_CRIT, "Error fetching %s #%i", host->sysName, outlet->n ); return APC_ERROR; } LOG( PIL_INFO, "%s outlet #%i '%s' is now %i", host->sysName, outlet->n, outlet->name, cur ); if( cur != want ) { ok = APC_NOT; } /* Question : skipping out as soon as we find a single outlet in the wrong state might be confusing. The operator will only see the outlet name up to the first that hasn't finished the reset. If multple outlets have different reset times, the list might look strange. However, in STONITH, wildcards aren't used. So it's a non-issue. */ } return ok; } /*******************************************************************/ /* Control the power on an outlet. Input : host host definition outlet name of the host to control op operation to perform 1- on 2- off 3- reset Returns : APC_OK APC_ERROR APC_TIMEOUT */ int host_power ( host_t *host, const char *outlet, int op ) { outlet_t outlets[ MAX_OUTLETS ]; time_t end, now; int check = op, set_once = 1, rv; rv = host_outlet_find( host, outlet, outlets ); if( rv == APC_ERROR ) { LOG( PIL_CRIT, "Error matching outlet '%s'", outlet ); return APC_ERROR; } if( rv == APC_NOT ) { LOG( PIL_CRIT, "No outlets match '%s'", outlet ); return APC_NOT; } time( &end ); end += host->timeout; if( check == APC_OP_RESET ) { /* 2 = reset, so we want to wait for a 1 */ check = 1; } do { if( set_once ) { rv = host_outlet_control( host, outlets, op ); if( rv == APC_ERROR ) { LOG( PIL_CRIT, "Error controling outlet '%s'", outlet ); return APC_ERROR; } } sleep(1); rv = host_outlet_check( host, outlets, check ); if( rv == APC_OK ) /* all matched */ return APC_OK; else if( rv != APC_NOT ) /* not all matched */ return APC_ERROR; /* anything else is an error */ if( op == APC_OP_RESET ) set_once = 0; /* only send reset command once */ time( &now ); if( now >= end ) { LOG( PIL_CRIT, "%s: Timeout waiting for outlets to become %s", host->sysName, ( check ? "on" : "off" ) ); return APC_TIMEOUT; } } while( 1 ); return APC_ERROR; }