/* * DisMan Schedule MIB: * Core implementation of the schedule handling behaviour */ #include #include #include #include #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 -1 ) { major = current/8; minor = current%8; for ( i=0; i>= 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); } }