diff options
Diffstat (limited to 'agent/mibgroup/disman/schedule/schedCore.c')
-rw-r--r-- | agent/mibgroup/disman/schedule/schedCore.c | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/agent/mibgroup/disman/schedule/schedCore.c b/agent/mibgroup/disman/schedule/schedCore.c new file mode 100644 index 0000000..c3db34f --- /dev/null +++ b/agent/mibgroup/disman/schedule/schedCore.c @@ -0,0 +1,515 @@ +/* + * DisMan Schedule MIB: + * Core implementation of the schedule handling behaviour + */ + +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-features.h> +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> +#include "disman/schedule/schedCore.h" +#include "utilities/iquery.h" + +netsnmp_feature_require(iquery) + +netsnmp_feature_child_of(sched_nextrowtime, netsnmp_unused) + +netsnmp_tdata *schedule_table; + + +#if !defined(HAVE_LOCALTIME_R) && !defined(localtime_r) +/* + * localtime_r() replacement for older MinGW versions. + * Note: this implementation is not thread-safe, while it should. + */ +struct tm * +localtime_r(const time_t * timer, struct tm *result) +{ + struct tm *result_p; + + result_p = localtime(timer); + if (result && result_p) + *result = *result_p; + return result_p; +} +#endif + + + /* + * Initialize the container for the schedule table, + * regardless of which initialisation routine is called first. + */ +void +init_schedule_container(void) +{ + DEBUGMSGTL(("disman:schedule:init", "init schedule container\n")); + if (!schedule_table) { + schedule_table = netsnmp_tdata_create_table("schedTable", 0); + DEBUGMSGTL(("disman:schedule:init", + "create schedule container(%p)\n", schedule_table)); + } +} + +/** Initializes the schedCore module */ +void +init_schedCore(void) +{ + /* + * Create a table structure for the schedule table + * This will typically be registered by the schedTable module + */ + DEBUGMSGTL(("disman:schedule:init", "Initializing core module\n")); + init_schedule_container(); +} + + +/* + * Callback to invoke a scheduled action + */ +static void +_sched_callback( unsigned int reg, void *magic ) +{ + struct schedTable_entry *entry = (struct schedTable_entry *)magic; + int ret; + netsnmp_variable_list assign; + + if ( !entry ) { + DEBUGMSGTL(("disman:schedule:callback", "missing entry\n")); + return; + } + entry->schedLastRun = time(NULL); + entry->schedTriggers++; + + DEBUGMSGTL(( "disman:schedule:callback", "assignment ")); + DEBUGMSGOID(("disman:schedule:callback", entry->schedVariable, + entry->schedVariable_len)); + DEBUGMSG(( "disman:schedule:callback", " = %ld\n", entry->schedValue)); + + memset(&assign, 0, sizeof(netsnmp_variable_list)); + snmp_set_var_objid(&assign, entry->schedVariable, entry->schedVariable_len); + snmp_set_var_typed_value(&assign, ASN_INTEGER, + (u_char *)&entry->schedValue, + sizeof(entry->schedValue)); + + ret = netsnmp_query_set( &assign, entry->session ); + if ( ret != SNMP_ERR_NOERROR ) { + DEBUGMSGTL(( "disman:schedule:callback", + "assignment failed (%d)\n", ret)); + entry->schedFailures++; + entry->schedLastFailure = ret; + time ( &entry->schedLastFailed ); + } + + sched_nextTime( entry ); +} + + + /* + * Internal utility routines to help interpret + * calendar-based schedule bit strings + */ +static u_char _masks[] = { /* 0xff, */ 0x7f, 0x3f, 0x1f, + 0x0f, 0x07, 0x03, 0x01, 0x00 }; +static u_char _bits[] = { 0x80, 0x40, 0x20, 0x10, + 0x08, 0x04, 0x02, 0x01 }; + +/* + * Are any of the bits set? + */ +static int +_bit_allClear( char *pattern, int len ) { + int i; + + for (i=0; i<len; i++) { + if ( pattern[i] != 0 ) + return 0; /* At least one bit set */ + } + return 1; /* All bits clear */ +} + +/* + * Is a particular bit set? + */ +static int +_bit_set( char *pattern, int bit ) { + int major, minor; + + major = bit/8; + minor = bit%8; + if ( pattern[major] & _bits[minor] ) { + return 1; /* Specified bit is set */ + } + return 0; /* Bit not set */ +} + +/* + * What is the next bit set? + * (after a specified point) + */ +static int +_bit_next( char *pattern, int current, size_t len ) { + char buf[ 8 ]; + int major, minor, i, j; + + /* Make a working copy of the bit pattern */ + memset( buf, 0, 8 ); + memcpy( buf, pattern, len ); + + /* + * If we're looking for the first bit after some point, + * then clear all earlier bits from the working copy. + */ + if ( current > -1 ) { + major = current/8; + minor = current%8; + for ( i=0; i<major; i++ ) + buf[i]=0; + buf[major] &= _masks[minor]; + } + + /* + * Look for the first bit that's set + */ + for ( i=0; i<(int)len; i++ ) { + if ( buf[i] != 0 ) { + major = i*8; + for ( j=0; j<8; j++ ) { + if ( buf[i] & _bits[j] ) { + return major+j; + } + } + } + } + return -1; /* No next bit */ +} + + +static int _daysPerMonth[] = { 31, 28, 31, 30, + 31, 30, 31, 31, + 30, 31, 30, 31, 29 }; + +static u_char _truncate[] = { 0xfe, 0xf0, 0xfe, 0xfc, + 0xfe, 0xfc, 0xfe, 0xfe, + 0xfc, 0xfe, 0xfc, 0xfe, 0xf8 }; + +/* + * What is the next day with a relevant bit set? + * + * Merge the forward and reverse day bits into a single + * pattern relevant for this particular month, + * and apply the standard _bit_next() call. + * Then check this result against the day of the week bits. + */ +static int +_bit_next_day( char *day_pattern, char weekday_pattern, + int day, int month, int year ) { + char buf[4]; + union { + char buf2[4]; + int int_val; + } rev; + int next_day, i; + struct tm tm_val; + + /* Make a working copy of the forward day bits ... */ + memset( buf, 0, 4 ); + memcpy( buf, day_pattern, 4 ); + + /* ... and another (right-aligned) of the reverse day bits */ + memset( rev.buf2, 0, 4 ); + memcpy( rev.buf2, day_pattern+4, 4 ); + rev.int_val >>= 2; + if ( buf[3] & 0x01 ) + rev.buf2[0] |= 0x40; + if ( month == 3 || month == 5 || + month == 8 || month == 10 ) + rev.int_val >>= 1; /* April, June, September, November */ + if ( month == 1 ) + rev.int_val >>= 3; /* February */ + if ( month == 12 ) + rev.int_val >>= 2; /* February (leap year) */ + + /* Combine the two bit patterns, and truncate to the month end */ + for ( i=0; i<4; i++ ) { + if ( rev.buf2[i] & 0x80 ) buf[3-i] |= 0x01; + if ( rev.buf2[i] & 0x40 ) buf[3-i] |= 0x02; + if ( rev.buf2[i] & 0x20 ) buf[3-i] |= 0x04; + if ( rev.buf2[i] & 0x10 ) buf[3-i] |= 0x08; + if ( rev.buf2[i] & 0x08 ) buf[3-i] |= 0x10; + if ( rev.buf2[i] & 0x04 ) buf[3-i] |= 0x20; + if ( rev.buf2[i] & 0x02 ) buf[3-i] |= 0x40; + if ( rev.buf2[i] & 0x01 ) buf[3-i] |= 0x80; + } + + buf[3] &= _truncate[ month ]; + + next_day = day-1; /* tm_day is 1-based, not 0-based */ + do { + next_day = _bit_next( buf, next_day, 4 ); + if ( next_day < 0 ) + return -1; + + /* + * Calculate the day of the week, and + * check this against the weekday pattern + */ + memset( &tm_val, 0, sizeof(struct tm)); + tm_val.tm_mday = next_day+1; + tm_val.tm_mon = month; + tm_val.tm_year = year; + mktime( &tm_val ); + } while ( !_bit_set( &weekday_pattern, tm_val.tm_wday )); + return next_day+1; /* Convert back to 1-based list */ +} + + +/* + * determine the time for the next scheduled action of a given entry + */ +void +sched_nextTime( struct schedTable_entry *entry ) +{ + time_t now; + struct tm now_tm, next_tm; + int rev_day, mon; + + time( &now ); + + if ( !entry ) { + DEBUGMSGTL(("disman:schedule:time", "missing entry\n")); + return; + } + + if ( entry->schedCallbackID ) + snmp_alarm_unregister( entry->schedCallbackID ); + + if (!(entry->flags & SCHEDULE_FLAG_ENABLED) || + !(entry->flags & SCHEDULE_FLAG_ACTIVE)) { + DEBUGMSGTL(("disman:schedule:time", "inactive entry\n")); + return; + } + + switch ( entry->schedType ) { + case SCHED_TYPE_PERIODIC: + if ( !entry->schedInterval ) { + DEBUGMSGTL(("disman:schedule:time", "periodic: no interval\n")); + return; + } + if ( entry->schedLastRun ) { + entry->schedNextRun = entry->schedLastRun + + entry->schedInterval; + } else { + entry->schedNextRun = now + entry->schedInterval; + } + DEBUGMSGTL(("disman:schedule:time", "periodic: (%ld) %s", + (long) entry->schedNextRun, ctime(&entry->schedNextRun))); + break; + + case SCHED_TYPE_ONESHOT: + if ( entry->schedLastRun ) { + DEBUGMSGTL(("disman:schedule:time", "one-shot: expired (%ld) %s", + (long) entry->schedNextRun, + ctime(&entry->schedNextRun))); + return; + } + /* Fallthrough */ + DEBUGMSGTL(("disman:schedule:time", "one-shot: fallthrough\n")); + case SCHED_TYPE_CALENDAR: + /* + * Check for complete time specification + * If any of the five fields have no bits set, + * the entry can't possibly match any time. + */ + if ( _bit_allClear( entry->schedMinute, 8 ) || + _bit_allClear( entry->schedHour, 3 ) || + _bit_allClear( entry->schedDay, 4+4 ) || + _bit_allClear( entry->schedMonth, 2 ) || + _bit_allClear(&entry->schedWeekDay, 1 )) { + DEBUGMSGTL(("disman:schedule:time", "calendar: incomplete spec\n")); + return; + } + + /* + * Calculate the next run time: + * + * If the current Month, Day & Hour bits are set + * calculate the next specified minute + * If this fails (or the current Hour bit is not set) + * use the first specified minute, + * and calculate the next specified hour + * If this fails (or the current Day bit is not set) + * use the first specified minute and hour + * and calculate the next specified day (in this month) + * If this fails (or the current Month bit is not set) + * use the first specified minute and hour + * calculate the next specified month, and + * the first specified day (in that month) + */ + + (void) localtime_r( &now, &now_tm ); + (void) localtime_r( &now, &next_tm ); + + next_tm.tm_mon=-1; + next_tm.tm_mday=-1; + next_tm.tm_hour=-1; + next_tm.tm_min=-1; + next_tm.tm_sec=0; + if ( _bit_set( entry->schedMonth, now_tm.tm_mon )) { + next_tm.tm_mon = now_tm.tm_mon; + rev_day = _daysPerMonth[ now_tm.tm_mon ] - now_tm.tm_mday; + if ( _bit_set( &entry->schedWeekDay, now_tm.tm_wday ) && + (_bit_set( entry->schedDay, now_tm.tm_mday-1 ) || + _bit_set( entry->schedDay, 31+rev_day ))) { + next_tm.tm_mday = now_tm.tm_mday; + + if ( _bit_set( entry->schedHour, now_tm.tm_hour )) { + next_tm.tm_hour = now_tm.tm_hour; + /* XXX - Check Fall timechange */ + next_tm.tm_min = _bit_next( entry->schedMinute, + now_tm.tm_min, 8 ); + } else { + next_tm.tm_min = -1; + } + + if ( next_tm.tm_min == -1 ) { + next_tm.tm_min = _bit_next( entry->schedMinute, -1, 8 ); + next_tm.tm_hour = _bit_next( entry->schedHour, + now_tm.tm_hour, 3 ); + } + } else { + next_tm.tm_hour = -1; + } + + if ( next_tm.tm_hour == -1 ) { + next_tm.tm_min = _bit_next( entry->schedMinute, -1, 8 ); + next_tm.tm_hour = _bit_next( entry->schedHour, -1, 3 ); + /* Handle leap years */ + mon = now_tm.tm_mon; + if ( mon == 1 && (now_tm.tm_year%4 == 0) ) + mon = 12; + next_tm.tm_mday = _bit_next_day( entry->schedDay, + entry->schedWeekDay, + now_tm.tm_mday, + mon, now_tm.tm_year ); + } + } else { + next_tm.tm_min = _bit_next( entry->schedMinute, -1, 2 ); + next_tm.tm_hour = _bit_next( entry->schedHour, -1, 3 ); + next_tm.tm_mday = -1; + next_tm.tm_mon = now_tm.tm_mon; + } + + while ( next_tm.tm_mday == -1 ) { + next_tm.tm_mon = _bit_next( entry->schedMonth, + next_tm.tm_mon, 2 ); + if ( next_tm.tm_mon == -1 ) { + next_tm.tm_year++; + next_tm.tm_mon = _bit_next( entry->schedMonth, + -1, 2 ); + } + /* Handle leap years */ + mon = next_tm.tm_mon; + if ( mon == 1 && (next_tm.tm_year%4 == 0) ) + mon = 12; + next_tm.tm_mday = _bit_next_day( entry->schedDay, + entry->schedWeekDay, + -1, mon, next_tm.tm_year ); + /* XXX - catch infinite loop */ + } + + /* XXX - Check for Spring timechange */ + + /* + * 'next_tm' now contains the time for the next scheduled run + */ + entry->schedNextRun = mktime( &next_tm ); + DEBUGMSGTL(("disman:schedule:time", "calendar: (%ld) %s", + (long) entry->schedNextRun, ctime(&entry->schedNextRun))); + return; + + default: + DEBUGMSGTL(("disman:schedule:time", "unknown type (%ld)\n", + entry->schedType)); + return; + } + entry->schedCallbackID = snmp_alarm_register( + entry->schedNextRun - now, + 0, _sched_callback, entry ); + return; +} + +#ifndef NETSNMP_FEATURE_REMOVE_SCHED_NEXTROWTIME +void +sched_nextRowTime( netsnmp_tdata_row *row ) +{ + sched_nextTime((struct schedTable_entry *) row->data ); +} +#endif /* NETSNMP_FEATURE_REMOVE_SCHED_NEXTROWTIME */ + +/* + * create a new row in the table + */ +netsnmp_tdata_row * +schedTable_createEntry(const char *schedOwner, const char *schedName) +{ + struct schedTable_entry *entry; + netsnmp_tdata_row *row; + + DEBUGMSGTL(("disman:schedule:entry", "creating entry (%s, %s)\n", + schedOwner, schedName)); + entry = SNMP_MALLOC_TYPEDEF(struct schedTable_entry); + if (!entry) + return NULL; + + row = netsnmp_tdata_create_row(); + if (!row) { + SNMP_FREE(entry); + return NULL; + } + row->data = entry; + /* + * Set the indexing for this entry, both in the row + * data structure, and in the table_data helper. + */ + if (schedOwner) { + memcpy(entry->schedOwner, schedOwner, strlen(schedOwner)); + netsnmp_tdata_row_add_index(row, ASN_OCTET_STR, + entry->schedOwner, strlen(schedOwner)); + } + else + netsnmp_tdata_row_add_index(row, ASN_OCTET_STR, "", 0 ); + + memcpy( entry->schedName, schedName, strlen(schedName)); + netsnmp_tdata_row_add_index(row, ASN_OCTET_STR, + entry->schedName, strlen(schedName)); + /* + * Set the (non-zero) default values in the row data structure. + */ + entry->schedType = SCHED_TYPE_PERIODIC; + entry->schedVariable_len = 2; /* .0.0 */ + + netsnmp_tdata_add_row(schedule_table, row); + return row; +} + + +/* + * remove a row from the table + */ +void +schedTable_removeEntry(netsnmp_tdata_row *row) +{ + struct schedTable_entry *entry; + + if (!row || !row->data) { + DEBUGMSGTL(("disman:schedule:entry", "remove: missing entry\n")); + return; /* Nothing to remove */ + } + entry = (struct schedTable_entry *) + netsnmp_tdata_remove_and_delete_row(schedule_table, row); + if (entry) { + DEBUGMSGTL(("disman:schedule:entry", "remove entry (%s, %s)\n", + entry->schedOwner, entry->schedName)); + SNMP_FREE(entry); + } +} |