| /* |
| * DisMan Schedule MIB: |
| * Core implementation of the schedule handling behaviour |
| */ |
| |
| #include <net-snmp/net-snmp-config.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_tdata *schedule_table; |
| |
| /* |
| * 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(%x)\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(0); |
| entry->schedTriggers++; |
| |
| DEBUGMSGTL(( "disman:schedule:callback", "assignment ")); |
| DEBUGMSGOID(("disman:schedule:callback", entry->schedVariable, |
| entry->schedVariable_len)); |
| DEBUGMSG(( "disman:schedule:callback", " = %d\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 char _masks[] = { /* 0xff, */ 0x7f, 0x3f, 0x1f, |
| 0x0f, 0x07, 0x03, 0x01, 0x00 }; |
| static 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<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 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 */ |
| } |
| |
| |
| #ifndef HAVE_LOCALTIME_R |
| struct tm * |
| localtime_r(const time_t *timep, struct tm *result) { |
| struct tm *tmp; |
| |
| tmp = localtime( timep ); |
| if ( tmp && result ) { |
| memcpy( result, tmp, sizeof(struct tm)); |
| } |
| |
| return (tmp ? result : NULL ); |
| } |
| #endif |
| |
| /* |
| * 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: (%d) %s", |
| entry->schedNextRun, |
| ctime(&entry->schedNextRun))); |
| break; |
| |
| case SCHED_TYPE_ONESHOT: |
| if ( entry->schedLastRun ) { |
| DEBUGMSGTL(("disman:schedule:time", "one-shot: expired (%d) %s", |
| 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) |
| */ |
| |
| localtime_r( &now, &now_tm ); |
| 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: (%d) %s", |
| entry->schedNextRun, |
| ctime(&entry->schedNextRun))); |
| return; |
| |
| default: |
| DEBUGMSGTL(("disman:schedule:time", "unknown type (%d)\n", |
| entry->schedType)); |
| return; |
| } |
| entry->schedCallbackID = snmp_alarm_register( |
| entry->schedNextRun - now, |
| 0, _sched_callback, entry ); |
| return; |
| } |
| |
| void |
| sched_nextRowTime( netsnmp_tdata_row *row ) |
| { |
| sched_nextTime((struct schedTable_entry *) row->data ); |
| } |
| |
| /* |
| * 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); |
| } |
| } |