예제 #1
0
/**
 * This is the logic that is used to take a parameter string
 * given for a specific field in the cron schedule and expand it
 * out into a range of int's that can be looked up quickly later on
 * We must be given the index number of the field we're going to parse
 * and a min/max for the range of values allowed for the attribute.
 * If the parameter is invalid, we will report an error and return false
 * This will prevent them from querying nextRunTime() for runtimes
 * 
 * @param attribute_idx - the index for the parameter in CronTab::attributes
 * @param min - the mininum value in the range for this parameter
 * @param max - the maximum value in the range for this parameter
 * @return true if we were able to create the range of values
 **/
bool
CronTab::expandParameter( int attribute_idx, int min, int max )
{
	MyString *param = this->parameters[attribute_idx];
	ExtArray<int> *list	= this->ranges[attribute_idx];
	
		//
		// Make sure the parameter is valid
		// The validation method will have already printed out
		// the error message to the log
		//
	MyString error;
	if ( ! CronTab::validateParameter(	attribute_idx,
										param->Value(),
										error ) ) {
		dprintf( D_ALWAYS, "%s", error.Value() );
			//
			// Store the error in case they want to email
			// the user to tell them that they goofed
			//
		CronTab::errorLog += error;
		return ( false );
	}
		//
		// Remove any spaces
		//
	param->replaceString(" ", "");
	
		//
		// Now here's the tricky part! We need to expand their parameter
		// out into a range that can be put in array of integers
		// First start by spliting the string by commas
		//
	param->Tokenize();
	const char *_token;
	while ( ( _token = param->GetNextToken( CRONTAB_DELIMITER, true ) ) != NULL ) {
		MyString token( _token );
		int cur_min = min, cur_max = max, cur_step = 1;
		
			// -------------------------------------------------
			// STEP VALUES
			// The step value is independent of whether we have
			// a range, the wildcard, or a single number.
			// -------------------------------------------------
		if ( token.find( CRONTAB_STEP ) > 0 ) {
				//
				// Just look for the step value to replace 
				// the current step value. The other code will
				// handle the rest
				//
			token.Tokenize();
			const char *_temp;
				//
				// Take out the numerator, keep it for later
				//
			const char *_numerator = token.GetNextToken( CRONTAB_STEP, true );
			if ( ( _temp = token.GetNextToken( CRONTAB_STEP, true ) ) != NULL ) {
				MyString stepStr( _temp );
				stepStr.trim();
				cur_step = atoi( stepStr.Value() );
			}
				//
				// Now that we have the denominator, put the numerator back
				// as the token. This makes it easier to parse later on
				//
			token = _numerator;
		} // STEP
		
			// -------------------------------------------------
			// RANGE
			// If it's a range, expand the range but make sure we 
			// don't go above/below our limits
			// Note that the find will ignore the token if the
			// range delimiter is in the first character position
			// -------------------------------------------------
		if ( token.find( CRONTAB_RANGE ) > 0 ) {
				//
				// Split out the integers
				//
			token.Tokenize();
			MyString *_temp;
			int value;
			
				//
				// Min
				//
			_temp = new MyString( token.GetNextToken( CRONTAB_RANGE, true ) );
			_temp->trim();
			value = atoi( _temp->Value() );
			cur_min = ( value >= min ? value : min );
			delete _temp;
				//
				// Max
				//
			_temp = new MyString( token.GetNextToken( CRONTAB_RANGE, true ) );
			_temp->trim();
			value = atoi( _temp->Value() );
			cur_max = ( value <= max ? value : max );
			delete _temp;
			
			// -------------------------------------------------
			// WILDCARD
			// This will select all values for the given range
			// -------------------------------------------------
		} else if ( token.find( CRONTAB_WILDCARD ) >= 0 ) {
				//
				// For this we do nothing since it will just 
				// be the min-max range
				//
				// Day of Week Special Case
				// The day of week specifier is kind of weird
				// If it's the wildcard, then it doesn't mean
				// include all like the other fields. The reason
				// why we don't want to do that is because later 
				// on we are going to expand the day of the week 
				// field to be actual dates in a month in order
				// to figure out when to run next, so we don't 
				// want to expand out the wildcard for all days
				// if the day of month field is restricted
				// Read the cron manpage!
				//
				if ( attribute_idx  == CRONTAB_DOW_IDX ) {
					continue;
				}
			// -------------------------------------------------
			// SINGLE VALUE
			// They just want a single value to be added
			// Note that a single value with a step like "2/3" will
			// work in this code but its meaningless unless its whole number
			// -------------------------------------------------
		} else {
				//
				// Replace the range to be just this value only if it
				// fits in the min/max range we were given
				//
			int value = atoi(token.Value());
			if ( value >= min && value <= max ) {
				cur_min = cur_max = value;
			}
		}
			//
			// Fill out the numbers based on the range using
			// the step value
			//
		int ctr;
		for ( ctr = cur_min; ctr <= cur_max; ctr++ ) {
				//
				// Day of Week Special Case
				// The crontab specifications lets Sunday be
				// represented with either a 0 or a 7. Our 
				// dayOfWeek() method in date_util.h uses 0-6
				// So if this the day of the week attribute and 
				// we are given a 7 for Sunday, just convert it
				// to a zero
				//
			int temp = ctr;
			if ( attribute_idx == CRONTAB_DOW_IDX && 
				 temp == CRONTAB_DAY_OF_WEEK_MAX ) {
				temp = CRONTAB_DAY_OF_WEEK_MIN;
			}
			
				//
		 		// Make sure this value isn't alreay added and 
		 		// that it falls in our step listing for the range
		 		//
			if ( ( ( temp % cur_step ) == 0 ) && !this->contains( *list, temp ) ) {
				list->add( temp );
		 	}
		} // FOR
	} // WHILE
		//
		// Sort! Makes life easier later on
		//
	this->sort( *list );	
	return ( true );
}
예제 #2
0
/**
 * Important helper function that actually does the grunt work of
 * find the next runtime. We need to be given an array of the different
 * time fields so that we can match the current time with different 
 * parameters. The function will recursively call itself until
 * it can find a match at the MINUTES level. If one recursive call
 * cannot find a match, it will return false and the previous level
 * will increment its range index by one and try to find a match. On the 
 * the subsequent call useFirst will be set to true meaning that the level
 * does not need to look for a value in the range that is greater than or
 * equal to the current time value. If the failing returns to the MONTHS level, 
 * that means we've exhausted all our possiblities from the current time to the
 * the end of the year. So we'll increase the year by 1 and try the same logic again
 * using January 1st as the starting point.
 * The array match will contain the timestamp information of the job's
 * next run time
 * 
 * @param curTime - an array of time attributes for where we start our calculations
 * @param match - an array of time attributes that is the next run time
 * @param attribute_idx - the index for the current parameter in CronTab::attributes
 * @param useFirst - whether we should base ourselves off of Jan 1st
 * @return true if we able to find a new run time
 **/
bool
CronTab::matchFields( int *curTime, int *match, int attribute_idx, bool useFirst )
{
		//
		// Whether we need to tell the next level above that they
		// should use the first element in their range. If we were told
		// to then certainly the next level will as well
		//
	bool nextUseFirst = useFirst;
		//
		// First initialize ourself to know that we don't have a match
		//
	match[attribute_idx] = -1;
	
		//
		// Special Day of Week Handling
		// In order to get the day of week stuff to work, we have 
		// to insert all the days of the month for the matching days of the
		// week in the range.
		//
	ExtArray<int> *curRange = NULL;
	if ( attribute_idx == CRONTAB_DOM_IDX ) {
			//
			// We have to take the current month & year
			// and convert the days of the week range into
			// days of the month
			//
			
			//Issue here is that range for dom will be 1-31
			//for * and if one doesn't specify day_of_month in a job file
		if (this->ranges[attribute_idx]->length()==CRONTAB_DAY_OF_MONTH_MAX){	
			if ((this->ranges[CRONTAB_DOW_IDX]->length()==CRONTAB_DAY_OF_WEEK_MAX)||
			   (this->ranges[CRONTAB_DOW_IDX]->length()==0)){ 
				//if both wildcards, use month range
				//if DOW range empty use DOM range
				curRange = new ExtArray<int>( *this->ranges[attribute_idx] );
			} else {
				//only wildcard in month, so use day of week range
				//this isn't quite right
				curRange = new ExtArray<int>( CRONTAB_DAY_OF_MONTH_MAX );
			}
		}else{
		      // get to here means DOM was specified
		      curRange = new ExtArray<int>( *this->ranges[attribute_idx] );
		}
		
		int firstDay = dayOfWeek( match[CRONTAB_MONTHS_IDX],
								  1,
								  match[CRONTAB_YEARS_IDX] );
		int ctr, cnt;
		for ( ctr = 0, cnt = this->ranges[CRONTAB_DOW_IDX]->getlast();
			  ctr <= cnt;
			  ctr++ ) {
				//
				// Now figure out all the days for this specific day of the week
				//
			int day = (this->ranges[CRONTAB_DOW_IDX]->getElementAt(ctr) - firstDay) + 1;
			while ( day <= CRONTAB_DAY_OF_MONTH_MAX ) {
				if (curRange && day > 0 && !this->contains( *curRange, day ) ) {
					curRange->add( day );
				}
				day += 7;
			} // WHILE
		} // FOR
			//
			// We have sort the list since we've added new elements
			//
		this->sort( *curRange );
		
		//
		// Otherwise we'll just use the unmodified range
		//
	} else {
		curRange = this->ranges[attribute_idx];
	}

		//
		// Find the first match for this field
		// If our value isn't in the list, then we'll take the next one
		//
	bool ret = false;
	int range_idx, cnt;
	for ( range_idx = 0, cnt = curRange->getlast();
		  range_idx <= cnt;
		  range_idx++ ) {
			//
			// Two ways to make a match:
			//
			//	1) The level below told us that we need to just 
			//	   the first value in our range if we can. 
			//	2) We need to select the first value in our range
			//	   that is greater than or equal to the current
			//	   time value for this field. This is usually
			//	   what we will do on the very first call to
			//	   us. If we fail and return back to the previous
			//	   level, when they call us again they'll ask
			//	   us to just use the first value that we can
			//
		int value = curRange->getElementAt( range_idx );
		if ( useFirst || value >= curTime[attribute_idx] ) {
				//
				// If this value is greater than the current time value,
				// ask the next level to useFirst
				//
			if ( value > curTime[attribute_idx] ) nextUseFirst = true;
				//
				// Day of Month Check!
				// If this day doesn't exist in this month for
				// the current year in our search, we have to skip it
				//
			if ( attribute_idx == CRONTAB_DOM_IDX ) {
				int maxDOM = daysInMonth( 	match[CRONTAB_MONTHS_IDX],
											match[CRONTAB_YEARS_IDX] );
				if ( value > maxDOM ) {
					continue;
				}
			}
			match[attribute_idx] = value;
				//
				// If we're the last field for the crontab (i.e. minutes),
				// then we should have a valid time now!
				//
			if ( attribute_idx == CRONTAB_MINUTES_IDX ) {
				ret = true;
				break;
				
				//
				// Now that we have a current value for this field, call to the
				// next field level and see if they can find a match using our
				// If we roll back then we'll want the next time around for the
				// next level to just use the first parameter in their range
				// if they can
				//
			} else {
				ret = this->matchFields( curTime, match, attribute_idx - 1, nextUseFirst );
				nextUseFirst = true;
				if ( ret ) break;
			}
		}
	} // FOR
		//
		// If the next level up failed, we need to have
		// special handling for months so we can roll over the year
		// While this may seem trivial it's needed so that we 
		// can change the year in the matching for leap years
		// We will call ourself to make a nice little loop!
		//
	if ( !ret && attribute_idx == CRONTAB_MONTHS_IDX ) {
		match[CRONTAB_YEARS_IDX]++;	
		ret = this->matchFields( curTime, match, attribute_idx, true );
	}
	
		//
		// We only need to delete curRange if we had
		// instantiated a new object for it
		//
	if ( attribute_idx == CRONTAB_DOM_IDX && curRange ) delete curRange;
	
	return ( ret );
}